diff --git a/.version b/.version index e4e09da..3565f57 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -0.27.32.53 \ No newline at end of file +0.27.33.54 \ No newline at end of file diff --git a/Netina.Api/Controllers/BlogCategoryController.cs b/Netina.Api/Controllers/BlogCategoryController.cs index 8a0c903..15c3a6d 100644 --- a/Netina.Api/Controllers/BlogCategoryController.cs +++ b/Netina.Api/Controllers/BlogCategoryController.cs @@ -82,6 +82,8 @@ public class BlogCategoryController : ICarterModule if (dto.ParentId != default) ent.SetParent(dto.ParentId); newEnt.Id = ent.Id; + newEnt.CreatedAt = ent.CreatedAt; + newEnt.CreatedBy = ent.CreatedBy; repositoryWrapper.SetRepository().Update(newEnt); await repositoryWrapper.SaveChangesAsync(cancellationToken); return TypedResults.Ok(); diff --git a/Netina.Api/Controllers/BlogController.cs b/Netina.Api/Controllers/BlogController.cs index bfb22cc..b515bf5 100644 --- a/Netina.Api/Controllers/BlogController.cs +++ b/Netina.Api/Controllers/BlogController.cs @@ -74,6 +74,8 @@ public class BlogController : ICarterModule throw new AppException("Blog not found"); var newEnt = Blog.Create(dto.Title, dto.Content, dto.Tags, dto.ReadingTime, dto.Summery, dto.IsSuggested, dto.CategoryId); newEnt.Id = ent.Id; + newEnt.CreatedAt = ent.CreatedAt; + newEnt.CreatedBy = ent.CreatedBy; repositoryWrapper.SetRepository().Update(newEnt); await repositoryWrapper.SaveChangesAsync(cancellationToken); return TypedResults.Ok(); diff --git a/Netina.Api/Controllers/PageController.cs b/Netina.Api/Controllers/PageController.cs index d8584b3..822ac0c 100644 --- a/Netina.Api/Controllers/PageController.cs +++ b/Netina.Api/Controllers/PageController.cs @@ -31,8 +31,19 @@ public class PageController : ICarterModule .WithDisplayName("Post Page") .HasApiVersion(1.0) .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManagePages)); + + + group.MapDelete("{id}", DeletePageByIdAsync) + .WithDisplayName("Delete Page") + .HasApiVersion(1.0) + .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ViewPages, ApplicationPermission.ManagePages)); + } - public async Task GetPagesAsync(Guid id, [FromServices] IPageService pageService, CancellationToken cancellationToken) + + private async Task DeletePageByIdAsync([FromRoute]Guid id,[FromServices]IPageService pageService,CancellationToken cancellationToken) + => TypedResults.Ok(await pageService.DeletePageAsync(id, cancellationToken)); + + public async Task GetPagesAsync([FromServices] IPageService pageService, CancellationToken cancellationToken) { return TypedResults.Ok(await pageService.GetPagesAsync(cancellationToken)); } diff --git a/Netina.Api/Netina.Api.csproj b/Netina.Api/Netina.Api.csproj index fe45458..da9792a 100644 --- a/Netina.Api/Netina.Api.csproj +++ b/Netina.Api/Netina.Api.csproj @@ -6,8 +6,8 @@ enable true Linux - 0.27.31.52 - 0.27.31.52 + 0.27.33.54 + 0.27.33.54 diff --git a/Netina.Api/Program.cs b/Netina.Api/Program.cs index a200ed6..c3d3365 100644 --- a/Netina.Api/Program.cs +++ b/Netina.Api/Program.cs @@ -8,7 +8,7 @@ string env = builder.Environment.IsDevelopment() == true ? "Development" : "Prod builder.Host.UseContentRoot(Directory.GetCurrentDirectory()); if (builder.Environment.IsDevelopment()) { - string projectName = "Hamyan"; + string projectName = "Vesmeh"; builder.Configuration.AddJsonFile($"AppSettings/appsettings.json").AddJsonFile($"AppSettings/appsettings.{env}{projectName}.json"); } diff --git a/Netina.Core/BaseServices/SiteMapService.cs b/Netina.Core/BaseServices/SiteMapService.cs index d614173..6a3f87c 100644 --- a/Netina.Core/BaseServices/SiteMapService.cs +++ b/Netina.Core/BaseServices/SiteMapService.cs @@ -12,12 +12,17 @@ public class SiteMapService : ISiteMapService { private readonly IUploadFileService _uploadFileService; private readonly IRepositoryWrapper _repositoryWrapper; + private readonly IPageService _pageService; private readonly SiteSettings _siteSetting; - public SiteMapService(IOptionsSnapshot snapshot, IUploadFileService uploadFileService, IRepositoryWrapper repositoryWrapper) + public SiteMapService(IOptionsSnapshot snapshot, + IUploadFileService uploadFileService, + IRepositoryWrapper repositoryWrapper, + IPageService pageService) { _uploadFileService = uploadFileService; _repositoryWrapper = repositoryWrapper; + _pageService = pageService; _siteSetting = snapshot.Value; } public async Task CreateSiteMapAsync() @@ -106,10 +111,77 @@ public class SiteMapService : ISiteMapService await CreateBlogsSiteMapsAsync(); await CreateBrandsSiteMapsAsync(); await CreateBlogCategoriesSiteMapsAsync(); + await CreatePagesSiteMapsAsync(); } + private async Task CreatePagesSiteMapsAsync() + { + var siteMapsUId = SiteMapUIds.Pages; + + var pages = await _pageService.GetPagesAsync(); + + XmlDocument doc = new XmlDocument(); + XmlDeclaration documentType = doc.CreateXmlDeclaration("1.0", "utf-8", null); + doc.AppendChild(documentType); + + //XmlNode declaration = doc.CreateNode(XmlNodeType.XmlDeclaration, "sitemap.xml", null); + //doc.AppendChild(declaration); + + XmlElement root = doc.CreateElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9"); + root.SetAttribute("xmlns:image", "http://www.google.com/schemas/sitemap-image/1.1"); + doc.AppendChild(root); + + foreach (var page in pages) + { + var productUrl = $"{_siteSetting.WebSiteUrl}/{page.Slug}"; + XmlElement urlElement = doc.CreateElement("url", doc.DocumentElement?.NamespaceURI); + root.AppendChild(urlElement); + + XmlElement loc = doc.CreateElement("loc", doc.DocumentElement?.NamespaceURI); + loc.InnerText = Path.Combine(productUrl); + urlElement.AppendChild(loc); + + XmlElement lastmod = doc.CreateElement("lastmod", doc.DocumentElement?.NamespaceURI); + lastmod.InnerText = page.ModifiedAt == DateTime.MinValue ? page.CreatedAt.ToString("yyyy-MM-dd") : page.ModifiedAt.ToString("yyyy-MM-dd"); + urlElement.AppendChild(lastmod); + + + XmlElement changeFeq = doc.CreateElement("changefreq", doc.DocumentElement?.NamespaceURI); + changeFeq.InnerText = "weekly"; + urlElement.AppendChild(changeFeq); + + + XmlElement priority = doc.CreateElement("priority", doc.DocumentElement?.NamespaceURI); + priority.InnerText = "0.8"; + urlElement.AppendChild(priority); + + } + + using var siteMapStream = new MemoryStream(); + await using var siteMapWriter = new XmlTextWriter(siteMapStream, Encoding.UTF8); + doc.WriteTo(siteMapWriter); + siteMapWriter.Flush(); + byte[] unZipBytes = siteMapStream.ToArray(); + + using (var compressedStream = new MemoryStream()) + await using (var zipStream = new GZipStream(compressedStream, CompressionMode.Compress)) + { + zipStream.Write(unZipBytes, 0, unZipBytes.Length); + zipStream.Close(); + var siteMapArray = compressedStream.ToArray(); + + await _uploadFileService.UploadFileByteAsync(new FileUploadRequest + { + FileBytes = siteMapArray, + ContentType = "text/plain", + FileName = $"{siteMapsUId}.gz", + FileUploadType = FileUploadType.SiteMap, + }); + } + } + private async Task CreateBrandsSiteMapsAsync() { var siteMapsUId = SiteMapUIds.Brands; diff --git a/Netina.Core/CoreServices/Abstracts/IPageService.cs b/Netina.Core/CoreServices/Abstracts/IPageService.cs index 1591c90..55c8e43 100644 --- a/Netina.Core/CoreServices/Abstracts/IPageService.cs +++ b/Netina.Core/CoreServices/Abstracts/IPageService.cs @@ -1,12 +1,9 @@ -using Netina.Common.Models; -using Netina.Domain.Dtos.RequestDtos; -using Netina.Domain.Dtos.SmallDtos; - -namespace Netina.Core.CoreServices.Abstracts; +namespace Netina.Core.CoreServices.Abstracts; public interface IPageService : IScopedDependency { Task GetPageAsync(Guid? id = null, string? pageName = null, string? pageSlug = null,string? type = null, CancellationToken cancellationToken=default); Task> GetPagesAsync(CancellationToken cancellationToken = default); Task CreatePageAsync(PageActionRequestDto entity, CancellationToken cancellationToken = default); + Task DeletePageAsync(Guid id,CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Netina.Core/CoreServices/PageService.cs b/Netina.Core/CoreServices/PageService.cs index e16d6ff..715437f 100644 --- a/Netina.Core/CoreServices/PageService.cs +++ b/Netina.Core/CoreServices/PageService.cs @@ -1,21 +1,16 @@ -using Netina.Common.Models.Api; -using Netina.Common.Models.Exception; -using Netina.Core.CoreServices.Abstracts; -using Netina.Domain; -using Netina.Domain.Dtos.RequestDtos; -using Netina.Domain.Dtos.SmallDtos; -using Netina.Domain.MartenEntities.Pages; -using Netina.Repository.Repositories.Marten; +using Netina.Domain.MartenEntities.Pages; namespace Netina.Core.CoreServices; public class PageService : IPageService { private readonly IMartenRepositoryWrapper _martenRepositoryWrapper; + private readonly ICurrentUserService _currentUserService; - public PageService(IMartenRepositoryWrapper martenRepositoryWrapperWrapper) + public PageService(IMartenRepositoryWrapper martenRepositoryWrapperWrapper, ICurrentUserService currentUserService) { _martenRepositoryWrapper = martenRepositoryWrapperWrapper; + _currentUserService = currentUserService; } public async Task GetPageAsync(Guid? id = null, string? pageName = null, string? pageSlug = null, string? type = null, CancellationToken cancellationToken = default) { @@ -25,7 +20,7 @@ public class PageService : IPageService else if (pageSlug != null) page = await _martenRepositoryWrapper.SetRepository().GetEntityAsync(entity => entity.Slug == pageSlug, cancellationToken); else if (pageName != null) - page = await _martenRepositoryWrapper.SetRepository().GetEntityAsync(entity => entity.Name == pageName, cancellationToken); + page = await _martenRepositoryWrapper.SetRepository().GetEntityAsync(entity => entity.Title == pageName, cancellationToken); else if (type != null) page = await _martenRepositoryWrapper.SetRepository().GetEntityAsync(entity => entity.Type == type, cancellationToken); if (page == null) @@ -39,7 +34,7 @@ public class PageService : IPageService Id = page.Id, IsCustomPage = page.IsCustomPage, IsHtmlBasePage = page.IsHtmlBasePage, - Name = page.Name, + Title = page.Title, Slug = page.Slug, Data = page.Data }; @@ -53,8 +48,6 @@ public class PageService : IPageService var pages = await _martenRepositoryWrapper.SetRepository().GetEntitiesAsync(cancellationToken); foreach (var page in pages) { - - var type = Assembly.GetAssembly(typeof(DomainConfig))?.GetType(page.Type); var dto = new BasePageSDto { Content = page.Content, @@ -62,9 +55,11 @@ public class PageService : IPageService Id = page.Id, IsCustomPage = page.IsCustomPage, IsHtmlBasePage = page.IsHtmlBasePage, - Name = page.Name, + Title = page.Title, Slug = page.Slug, - Data = page.Data + Data = page.Data, + CreatedAt = page.CreatedAt, + ModifiedAt = page.ModifiedAt }; sDtos.Add(dto); } @@ -77,16 +72,30 @@ public class PageService : IPageService { Content = entity.Content, Description = entity.Description, - Id = entity.Id, + Id = Guid.NewGuid(), IsCustomPage = entity.IsCustomPage, IsHtmlBasePage = entity.IsHtmlBasePage, - Name = entity.Name, + Title = entity.Title, Type = entity.Type, Slug = entity.Slug, + CreatedAt = DateTime.Now, + CreatedBy = _currentUserService.UserName ?? string.Empty }; - var type = Assembly.GetAssembly(typeof(DomainConfig))?.GetType(entity.Type); - basePage.Data = JsonConvert.SerializeObject(((JsonElement)entity.Data).Deserialize(type)); + if (!basePage.Type.IsNullOrEmpty()) + { + var type = Assembly.GetAssembly(typeof(DomainConfig))?.GetType(entity.Type); + basePage.Data = JsonConvert.SerializeObject(((JsonElement)entity.Data).Deserialize(type)); + } await _martenRepositoryWrapper.SetRepository().AddOrUpdateEntityAsync(basePage, cancellationToken); return true; } + + public async Task DeletePageAsync(Guid id, CancellationToken cancellationToken = default) + { + var page = await _martenRepositoryWrapper.SetRepository().GetEntityAsync(p => p.Id == id, cancellationToken); + if (page == null) + throw new AppException("Page not found", ApiResultStatusCode.NotFound); + await _martenRepositoryWrapper.SetRepository().RemoveEntityAsync(page,cancellationToken); + return true; + } } \ No newline at end of file diff --git a/Netina.Core/Models/SiteMapUIds.cs b/Netina.Core/Models/SiteMapUIds.cs index 5d558c2..843b38f 100644 --- a/Netina.Core/Models/SiteMapUIds.cs +++ b/Netina.Core/Models/SiteMapUIds.cs @@ -5,12 +5,14 @@ public static class SiteMapUIds public const string Brands = "662AF2DC6A2746FA88191F1F3DA94164"; public const string Blogs = "4C2F0C2A7A3E41268702D12FDDDB837F"; public const string BlogCategories = "B5FF333DC4FF4BB4A309CE3AA32CE45A"; + public const string Pages = "B463B6C4CC53432C822D79934F528D3E"; public static List AllSiteMapsUIds => new List { BlogCategories, Categories, Blogs, - Brands + Brands, + Pages }; } diff --git a/Netina.Domain/Dtos/RequestDtos/PageActionRequestDto.cs b/Netina.Domain/Dtos/RequestDtos/PageActionRequestDto.cs index 3c5b089..8b0ca97 100644 --- a/Netina.Domain/Dtos/RequestDtos/PageActionRequestDto.cs +++ b/Netina.Domain/Dtos/RequestDtos/PageActionRequestDto.cs @@ -3,7 +3,7 @@ public class PageActionRequestDto { public Guid Id { get; set; } - public string Name { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string Content { get; set; } = string.Empty; public bool IsCustomPage { get; set; } diff --git a/Netina.Domain/Dtos/SmallDtos/BasePageSDto.cs b/Netina.Domain/Dtos/SmallDtos/BasePageSDto.cs index 6b9b679..994be04 100644 --- a/Netina.Domain/Dtos/SmallDtos/BasePageSDto.cs +++ b/Netina.Domain/Dtos/SmallDtos/BasePageSDto.cs @@ -5,13 +5,14 @@ namespace Netina.Domain.Dtos.SmallDtos; public class BasePageSDto : BaseDto { - public string Name { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string Content { get; set; } = string.Empty; public bool IsCustomPage { get; set; } public bool IsHtmlBasePage { get; set; } public string Slug { get; set; } = string.Empty; public string Data { get; set; } = string.Empty; + public DateTime ModifiedAt { get; set; } public T GetData() => JsonConvert.DeserializeObject(Data); } \ No newline at end of file diff --git a/Netina.Domain/MartenEntities/Pages/BasePage.cs b/Netina.Domain/MartenEntities/Pages/BasePage.cs index 92fb99f..704dd2b 100644 --- a/Netina.Domain/MartenEntities/Pages/BasePage.cs +++ b/Netina.Domain/MartenEntities/Pages/BasePage.cs @@ -2,7 +2,7 @@ public class BasePage : MartenEntity { - public string Name { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public string Content { get; set; } = string.Empty; public bool IsCustomPage { get; set; } diff --git a/Netina.Infrastructure/Marten/MartenRepository.cs b/Netina.Infrastructure/Marten/MartenRepository.cs index e2cf68a..f894b9d 100644 --- a/Netina.Infrastructure/Marten/MartenRepository.cs +++ b/Netina.Infrastructure/Marten/MartenRepository.cs @@ -1,8 +1,4 @@ using Marten; -using Netina.Common.Models.Api; -using Netina.Common.Models.Entity; -using Netina.Common.Models.Exception; -using Netina.Repository.Repositories.Marten; namespace Netina.Infrastructure.Marten; @@ -45,18 +41,22 @@ public class MartenRepository : IMartenRepository return entity; } - public async Task AddOrUpdateEntityAsync(TMartenEntity setting, CancellationToken cancellation) + public async Task AddOrUpdateEntityAsync(TMartenEntity entity, CancellationToken cancellation) { - if (setting == null) - throw new AppException($"{nameof(setting)} is null", ApiResultStatusCode.BadRequest); + if (entity == null) + throw new AppException($"{nameof(entity)} is null", ApiResultStatusCode.BadRequest); await using var session = _documentStore.LightweightSession(); - session.Store(setting); + session.Store(entity); await session.SaveChangesAsync(cancellation); } - public Task RemoveEntityAsync(CancellationToken cancellation) + public async Task RemoveEntityAsync(TMartenEntity entity, CancellationToken cancellation) { - throw new NotImplementedException(); + if (entity == null) + throw new AppException($"{nameof(entity)} is null", ApiResultStatusCode.BadRequest); + await using var session = _documentStore.LightweightSession(); + session.Delete(entity); + await session.SaveChangesAsync(cancellation); } } \ No newline at end of file diff --git a/Netina.Repository/Repositories/Marten/IMartenRepository.cs b/Netina.Repository/Repositories/Marten/IMartenRepository.cs index 8ea6e41..441659d 100644 --- a/Netina.Repository/Repositories/Marten/IMartenRepository.cs +++ b/Netina.Repository/Repositories/Marten/IMartenRepository.cs @@ -4,12 +4,12 @@ namespace Netina.Repository.Repositories.Marten; public interface IMartenRepository : IScopedDependency where TMartenEntity : IMartenEntity { - Task> GetEntitiesAsync(CancellationToken cancellation); - Task> GetEntitiesAsync(Expression> expression, CancellationToken cancellation); + Task> GetEntitiesAsync(CancellationToken cancellation = default); + Task> GetEntitiesAsync(Expression> expression, CancellationToken cancellation = default); - Task GetEntityAsync(Guid id, CancellationToken cancellation); - Task GetEntityAsync(Expression> expression, CancellationToken cancellation); + Task GetEntityAsync(Guid id, CancellationToken cancellation = default); + Task GetEntityAsync(Expression> expression, CancellationToken cancellation = default); - Task AddOrUpdateEntityAsync(TMartenEntity setting, CancellationToken cancellation); - Task RemoveEntityAsync(CancellationToken cancellation); + Task AddOrUpdateEntityAsync(TMartenEntity entity, CancellationToken cancellation = default); + Task RemoveEntityAsync(TMartenEntity entity, CancellationToken cancellation = default); } \ No newline at end of file