Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) &&
IsDateBetween(x.ModifiedDate, startDate, endDate));

var result = blobs
.Select(x => publishingService.GetRelativeDraftUrl(x.RelativeUrl, draft: 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 All @@ -76,5 +82,11 @@ private async Task<int> CountContentItemsRecursive(string folderUrl, IBlobStorag

return result;
}

private static bool IsDateBetween(DateTime? modifiedDate, DateTime? startDate, DateTime? endDate)
{
return (startDate == null || modifiedDate >= startDate) &&
(endDate == null || modifiedDate <= endDate);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ private static void SetFileStatusByName(ContentFile file)
var isDraft = file.Name.EndsWith("-draft");
file.HasChanges = isDraft;
file.Published = !isDraft;
file.Name = isDraft

file.Name = file.Name.EndsWith("-draft")
? file.Name.Substring(0, file.Name.Length - "-draft".Length)
: file.Name;
}
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("file1.md", "file2.md", 2)]
[InlineData("file1.md-draft", "file2.md-draft", 2)]
[InlineData("file1.md-draft", "file2.md", 2)]
[InlineData("file1.md-draft", "file1.md", 1)]
public async Task GetStorePagesCount_IsCorrect(string filename1, string filename2, int expectedCount)
{
var sut = GetService([filename1, filename2]);
var result = await sut.GetStorePagesCountAsync(StoreId);
Assert.Equal(expectedCount, result);
}

private static 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