Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static string GenerateId(string storeId, string contentType, ContentItem
public static (string storeId, string contentType, string relativeUrl) ParseId(string id)
{
var decoded = Encoding.ASCII.GetString(Convert.FromBase64String(id.Replace('-', '=')));
var result = decoded.Split(new[] { "::" }, StringSplitOptions.RemoveEmptyEntries);
var result = decoded.Split(["::"], StringSplitOptions.RemoveEmptyEntries);

if (result.Length == 3)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,20 @@ namespace VirtoCommerce.ContentModule.Data.Services
public class ContentStatisticService(
IBlobContentStorageProviderFactory blobContentStorageProviderFactory,
IContentPathResolver contentPathResolver,
IContentItemTypeRegistrar contentItemTypeRegistrar)
IContentItemTypeRegistrar contentItemTypeRegistrar,
IPublishingService publishingService)
: IContentStatisticService
{
public async Task<int> GetStorePagesCountAsync(string storeId)
{
var (contentStorageProvider, path) = Prepare(storeId, ContentConstants.ContentTypes.Pages);
var contentStorageProvider = Prepare(storeId, ContentConstants.ContentTypes.Pages);
var result = await CountContentItemsRecursive(folderUrl: null, contentStorageProvider, startDate: null, endDate: null, ContentConstants.ContentTypes.Blogs);
return result;
}

public async Task<int> GetStoreChangedPagesCountAsync(string storeId, DateTime? startDate, DateTime? endDate)
{
var (contentStorageProvider, path) = Prepare(storeId, ContentConstants.ContentTypes.Pages);
var contentStorageProvider = Prepare(storeId, ContentConstants.ContentTypes.Pages);
var result = await CountContentItemsRecursive(folderUrl: null, contentStorageProvider, startDate, endDate);
return result;
}
Expand All @@ -43,27 +44,32 @@ public async Task<int> GetStoreBlogsCountAsync(string storeId)

private async Task<int> GetFoldersCount(string storeId, string contentType)
{
var (contentStorageProvider, targetPath) = Prepare(storeId, contentType);
var contentStorageProvider = Prepare(storeId, contentType);
var folders = await contentStorageProvider.SearchAsync(folderUrl: null, keyword: null);
var result = folders.Results.OfType<BlobFolder>().Count();
return result;
}

private (IBlobContentStorageProvider provider, string targetPath) Prepare(string storeId, string contentType)
private IBlobContentStorageProvider Prepare(string storeId, string contentType)
{
var targetPath = contentPathResolver.GetContentBasePath(contentType, storeId);
var contentStorageProvider = blobContentStorageProviderFactory.CreateProvider(targetPath);
return (contentStorageProvider, targetPath);
return contentStorageProvider;
}

private async Task<int> CountContentItemsRecursive(string folderUrl, IBlobStorageProvider blobContentStorageProvider, DateTime? startDate, DateTime? endDate, string excludedFolderName = null)
{
var searchResult = await blobContentStorageProvider.SearchAsync(folderUrl, keyword: null);
var folders = searchResult.Results.OfType<BlobFolder>();
var blobs = searchResult.Results.OfType<BlobInfo>()
.Where(x => contentItemTypeRegistrar.IsRegisteredContentItemType(x.RelativeUrl));
.Where(x => contentItemTypeRegistrar.IsRegisteredContentItemType(x.RelativeUrl))
.Where(x => (startDate == null || x.ModifiedDate >= startDate) && (endDate == null || x.ModifiedDate <= endDate));

var result = blobs
.Select(x => publishingService.GetRelativeDraftUrl(x.RelativeUrl, false))
.Distinct()
.Count();

var result = blobs.Count(x => (startDate == null || x.ModifiedDate >= startDate) && (endDate == null || x.ModifiedDate <= endDate));
var children = folders.Where(x =>
(excludedFolderName.IsNullOrEmpty() || !x.Name.EqualsIgnoreCase(excludedFolderName)) // exclude predefined folders
&& x.Url != folderUrl); // the simplest way to avoid loop (i.e. "https://qademovc3.blob.core.windows.net/cms/Pages/Electronics/blogs/https://")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,14 @@ private static void SetFileStatusByName(ContentFile file)
var isDraft = file.Name.EndsWith("-draft");
file.HasChanges = isDraft;
file.Published = !isDraft;
file.Name = isDraft
? file.Name.Substring(0, file.Name.Length - "-draft".Length)
: file.Name;

file.Name = RemoveDraftSuffix(file.Name);
file.RelativeUrl = RemoveDraftSuffix(file.RelativeUrl);
return;

string RemoveDraftSuffix(string name) => isDraft
? name[..^"-draft".Length]
: name;
}

public string GetRelativeDraftUrl(string source, bool draft)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ public class ContentController(
IConfiguration configuration)
: Controller
{
private readonly IPublishingService _publishingService = publishingService;
private static readonly FormOptions _defaultFormOptions = new();

/// <summary>
Expand Down Expand Up @@ -93,8 +92,8 @@ public async Task<ActionResult> DeleteContent(string contentType, string storeId
foreach (var url in urls)
{
var isFolder = true;
var draftUrl = _publishingService.GetRelativeDraftUrl(url, true);
var publishedUrl = _publishingService.GetRelativeDraftUrl(url, false);
var draftUrl = publishingService.GetRelativeDraftUrl(url, true);
var publishedUrl = publishingService.GetRelativeDraftUrl(url, false);
if (await contentService.ItemExistsAsync(contentType, storeId, draftUrl))
{
urlsToRemove.Add(draftUrl);
Expand All @@ -116,7 +115,7 @@ public async Task<ActionResult> DeleteContent(string contentType, string storeId
}

/// <summary>
/// Return streamed data for requested by relativeUrl content (Used to prevent Cross domain requests in manager)
/// Return streamed data for requested by relativeUrl content (Used to prevent Cross domain requests in manager)
/// </summary>
/// <param name="contentType">possible values Themes or Pages</param>
/// <param name="storeId">Store id</param>
Expand All @@ -131,13 +130,13 @@ public async Task<ActionResult<byte[]>> GetContentItemDataStream(string contentT
if (draft)
{
// use the draft logic, try to load the draft file, and if it isn't found, load to published version
var draftUrl = _publishingService.GetRelativeDraftUrl(relativeUrl, true);
var draftUrl = publishingService.GetRelativeDraftUrl(relativeUrl, true);
if (await contentService.ItemExistsAsync(contentType, storeId, draftUrl))
{
var result = await contentService.GetItemStreamAsync(contentType, storeId, draftUrl);
return File(result, MimeTypeResolver.ResolveContentType(relativeUrl));
}
var sourceUrl = _publishingService.GetRelativeDraftUrl(relativeUrl, false);
var sourceUrl = publishingService.GetRelativeDraftUrl(relativeUrl, false);
if (await contentService.ItemExistsAsync(contentType, storeId, sourceUrl))
{
var result = await contentService.GetItemStreamAsync(contentType, storeId, sourceUrl);
Expand Down Expand Up @@ -176,7 +175,7 @@ public async Task<ActionResult<ContentItem[]>> SearchContent(string contentType,
criteria.Keyword = keyword;
var result = await contentFileService.FilterItemsAsync(criteria);
var folders = result.Where(x => x is not ContentFile);
var files = await _publishingService.SetFilesStatuses(result.OfType<ContentFile>());
var files = await publishingService.SetFilesStatuses(result.OfType<ContentFile>());
var response = folders.Union(files);
return Ok(response);
}
Expand All @@ -194,7 +193,7 @@ public async Task<ActionResult<ContentItem[]>> FulltextSearchContent([FromBody]
criteria.Skip = 0;
criteria.Take = 100;
var result = await fullTextContentSearchService.SearchAllAsync(criteria);
var response = _publishingService.SetFilesStatuses(result);
var response = publishingService.SetFilesStatuses(result);
return Ok(response);
}

Expand All @@ -220,22 +219,22 @@ public ActionResult GetContentFullTextSearchEnabled()
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
public async Task<ActionResult> MoveContent(string contentType, string storeId, [FromQuery] string oldUrl, [FromQuery] string newUrl)
{
var publishedSrc = _publishingService.GetRelativeDraftUrl(oldUrl, false);
var unpublishedSrc = _publishingService.GetRelativeDraftUrl(oldUrl, true);
var publishedSrc = publishingService.GetRelativeDraftUrl(oldUrl, false);
var unpublishedSrc = publishingService.GetRelativeDraftUrl(oldUrl, true);

var isFile = false;

if (await contentService.ItemExistsAsync(contentType, storeId, publishedSrc))
{
isFile = true;
var publishedDest = _publishingService.GetRelativeDraftUrl(newUrl, false);
var publishedDest = publishingService.GetRelativeDraftUrl(newUrl, false);
await contentService.MoveContentAsync(contentType, storeId, publishedSrc, publishedDest);
}

if (await contentService.ItemExistsAsync(contentType, storeId, unpublishedSrc))
{
isFile = true;
var unpublishedDest = _publishingService.GetRelativeDraftUrl(newUrl, true);
var unpublishedDest = publishingService.GetRelativeDraftUrl(newUrl, true);
await contentService.MoveContentAsync(contentType, storeId, unpublishedSrc, unpublishedDest);
}

Expand Down Expand Up @@ -302,7 +301,7 @@ await contentService.ItemExistsAsync(contentType, storeId, Path.Combine(path, $"
destFile = Path.Combine(path, $"{filename}_{index}{langSuffix}{ext}");
}

destFile = _publishingService.GetRelativeDraftUrl(destFile, true);
destFile = publishingService.GetRelativeDraftUrl(destFile, true);
await contentService.CopyFileAsync(contentType, storeId, srcFile, destFile);
return NoContent();
}
Expand Down Expand Up @@ -401,7 +400,7 @@ public async Task<ActionResult<ContentItem[]>> UploadContent(string contentType,
{
var fileName = Path.GetFileName(contentDisposition.FileName.Value ?? contentDisposition.Name.Value.Replace("\"", string.Empty));

fileName = _publishingService.GetRelativeDraftUrl(fileName, draft);
fileName = publishingService.GetRelativeDraftUrl(fileName, draft);

var file = await contentService.SaveContentAsync(contentType, storeId, folderUrl, fileName, section.Body);
retVal.Add(file);
Expand All @@ -420,15 +419,17 @@ public async Task<ActionResult<ContentItem[]>> UploadContent(string contentType,

ContentCacheRegion.ExpireContent(($"content-{storeId}"));

return Ok(retVal.ToArray());
var files = await publishingService.SetFilesStatuses(retVal);

return Ok(files);
}

[HttpPost]
[Route("publishing")]
[Authorize(Permissions.Create)]
public async Task<ActionResult> Publishing(string contentType, string storeId, [FromQuery] string relativeUrl, [FromQuery] bool publish)
{
await _publishingService.PublishingAsync(contentType, storeId, relativeUrl, publish);
await publishingService.PublishingAsync(contentType, storeId, relativeUrl, publish);
return Ok();
}

Expand All @@ -437,7 +438,7 @@ public async Task<ActionResult> Publishing(string contentType, string storeId, [
[Authorize(Permissions.Create)]
public async Task<ActionResult<FilePublishStatus>> PublishStatus(string contentType, string storeId, [FromQuery] string relativeUrl)
{
var result = await _publishingService.PublishStatusAsync(contentType, storeId, relativeUrl);
var result = await publishingService.PublishStatusAsync(contentType, storeId, relativeUrl);
return Ok(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ angular.module('virtoCommerce.contentModule')
blade.isLoading = false;
var needRefresh = true;
blade.currentEntity = Object.assign(blade.currentEntity, result[0]);
blade.published = blade.currentEntity.published;
angular.copy(blade.currentEntity, blade.origEntity);
if (blade.isNew) {
$scope.bladeClose();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Moq;
using VirtoCommerce.AssetsModule.Core.Assets;
using VirtoCommerce.ContentModule.Core.Search;
using VirtoCommerce.ContentModule.Core.Services;
using VirtoCommerce.ContentModule.Data.Services;
using Xunit;

namespace VirtoCommerce.ContentModule.Tests;

public class ContentStatisticServiceTests
{
private const string StoreId = "TestStore";

[Theory]
[InlineData(2, "file1.md", "file2.md")]
[InlineData(2, "file1.md-draft", "file2.md-draft")]
[InlineData(2, "file1.md-draft", "file2.md")]
[InlineData(1, "file1.md-draft", "file1.md")]
public async Task GetStorePagesCount_IsCorrect(int count, params string[] files)
{
var sut = GetService(files);
var result = await sut.GetStorePagesCountAsync(StoreId);
Assert.Equal(count, result);
}

private ContentStatisticService GetService(params string[] files)
{
var blobContentProviderFactory = new BlobContentStorageProviderStub(files);
var contentPathResolver = new ContentPathResolverStub();

var contentItemPathRegistrar = new Mock<IContentItemTypeRegistrar>();
contentItemPathRegistrar.Setup(x => x.IsRegisteredContentItemType(It.IsAny<string>())).Returns(true);

var contentService = new Mock<IContentService>();
var publishingService = new PublishingServices(contentService.Object);
var result = new ContentStatisticService(blobContentProviderFactory, contentPathResolver, contentItemPathRegistrar.Object, publishingService);
return result;
}

private class ContentPathResolverStub : IContentPathResolver
{
public string GetContentBasePath(string contentType, string storeId, string themeName = null)
{
return string.Empty;
}
}

private class BlobContentStorageProviderStub(string[] files) : IBlobContentStorageProviderFactory
{
public IBlobContentStorageProvider CreateProvider(string basePath)
{
return new ContentBlobStorageProviderStub(files.ToList());
}
}

private class ContentBlobStorageProviderStub(List<string> files) : IBlobContentStorageProvider
{
public Task DeleteAsync(string blobUrl)
{
files.Remove(blobUrl);
return Task.CompletedTask;
}

public Task<bool> ExistsAsync(string blobUrl)
{
var exists = files.Contains(blobUrl);
return Task.FromResult(exists);
}

public Task<BlobEntrySearchResult> SearchAsync(string folderUrl, string keyword)
{
var result = new BlobEntrySearchResult
{
Results = files.Select(x => new BlobInfo { Name = x, RelativeUrl = x }).Cast<BlobEntry>().ToList(),
TotalCount = files.Count
};
return Task.FromResult(result);
}

public Task<BlobInfo> GetBlobInfoAsync(string blobUrl)
{
var result = new BlobInfo();
return Task.FromResult(result);
}

public Task CreateFolderAsync(BlobFolder folder)
{
return Task.CompletedTask;
}

public Stream OpenRead(string blobUrl)
{
throw new NotImplementedException();
}

public Task<Stream> OpenReadAsync(string blobUrl)
{
throw new NotImplementedException();
}

public Stream OpenWrite(string blobUrl)
{
throw new NotImplementedException();
}

public Task<Stream> OpenWriteAsync(string blobUrl)
{
throw new NotImplementedException();
}

public Task RemoveAsync(string[] urls)
{
throw new NotImplementedException();
}

public void Move(string srcUrl, string destUrl)
{
throw new NotImplementedException();
}

public Task MoveAsyncPublic(string srcUrl, string destUrl)
{
throw new NotImplementedException();
}

public void Copy(string srcUrl, string destUrl)
{
throw new NotImplementedException();
}

public Task CopyAsync(string srcUrl, string destUrl)
{
throw new NotImplementedException();
}

public Task UploadAsync(string blobUrl, Stream content, string contentType, bool overwrite = true)
{
throw new NotImplementedException();
}

public string GetAbsoluteUrl(string blobKey)
{
throw new NotImplementedException();
}
}
}
Loading
Loading