Skip to content

Commit 7072746

Browse files
authored
Merge branch 'dev' into fix/VCST-2474
2 parents 4f5b5bb + a4758e7 commit 7072746

22 files changed

+232
-162
lines changed

Directory.Build.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</PropertyGroup>
1010

1111
<PropertyGroup>
12-
<VersionPrefix>3.868.0</VersionPrefix>
12+
<VersionPrefix>3.872.0</VersionPrefix>
1313
<VersionSuffix></VersionSuffix>
1414
<VersionSuffix Condition=" '$(VersionSuffix)' != '' AND '$(BuildNumber)' != '' ">$(VersionSuffix)-$(BuildNumber)</VersionSuffix>
1515
<NoWarn>$(NoWarn);S3875;S4457</NoWarn>

src/VirtoCommerce.Platform.Web/Controllers/Api/ModulesController.cs

+124-52
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using Microsoft.AspNetCore.Http.Features;
1212
using Microsoft.AspNetCore.Mvc;
1313
using Microsoft.AspNetCore.WebUtilities;
14+
using Microsoft.Extensions.FileProviders;
1415
using Microsoft.Extensions.Options;
1516
using Microsoft.Net.Http.Headers;
1617
using VirtoCommerce.Platform.Core;
@@ -42,6 +43,7 @@ public class ModulesController : Controller
4243
private readonly IPlatformRestarter _platformRestarter;
4344
private static readonly object _lockObject = new object();
4445
private static readonly FormOptions _defaultFormOptions = new FormOptions();
46+
private readonly ILocalModuleCatalog _localModuleCatalog;
4547

4648
public ModulesController(
4749
IExternalModuleCatalog externalModuleCatalog,
@@ -52,7 +54,8 @@ public ModulesController(
5254
IOptions<PlatformOptions> platformOptions,
5355
IOptions<ExternalModuleCatalogOptions> externalModuleCatalogOptions,
5456
IOptions<LocalStorageModuleCatalogOptions> localStorageModuleCatalogOptions,
55-
IPlatformRestarter platformRestarter)
57+
IPlatformRestarter platformRestarter,
58+
ILocalModuleCatalog localModuleCatalog)
5659
{
5760
_externalModuleCatalog = externalModuleCatalog;
5861
_moduleInstaller = moduleInstaller;
@@ -63,6 +66,7 @@ public ModulesController(
6366
_externalModuleCatalogOptions = externalModuleCatalogOptions.Value;
6467
_localStorageModuleCatalogOptions = localStorageModuleCatalogOptions.Value;
6568
_platformRestarter = platformRestarter;
69+
_localModuleCatalog = localModuleCatalog;
6670
}
6771

6872
/// <summary>
@@ -90,11 +94,48 @@ public ActionResult<ModuleDescriptor[]> GetModules()
9094
{
9195
EnsureModulesCatalogInitialized();
9296

93-
var retVal = _externalModuleCatalog.Modules.OfType<ManifestModuleInfo>().OrderBy(x => x.Id).ThenBy(x => x.Version)
94-
.Select(x => new ModuleDescriptor(x))
95-
.ToArray();
97+
var allModules = _externalModuleCatalog.Modules
98+
.OfType<ManifestModuleInfo>()
99+
.OrderBy(x => x.Id)
100+
.ThenBy(x => x.Version)
101+
.Select(x => new ModuleDescriptor(x))
102+
.ToList();
96103

97-
return Ok(retVal);
104+
_localModuleCatalog.Initialize();
105+
var localModules = _localModuleCatalog.Modules.OfType<ManifestModuleInfo>().ToDictionary(x => x.Id);
106+
107+
foreach (var module in allModules.Where(x => !string.IsNullOrEmpty(x.IconUrl)))
108+
{
109+
module.IconUrl = localModules.TryGetValue(module.Id, out var localModule) && IconFileExists(localModule)
110+
? localModule.IconUrl
111+
: null;
112+
}
113+
114+
return Ok(allModules);
115+
}
116+
117+
private static bool IconFileExists(ManifestModuleInfo module)
118+
{
119+
// PathString should start from "/"
120+
var moduleIconUrl = module.IconUrl;
121+
if (!moduleIconUrl.StartsWith('/'))
122+
{
123+
moduleIconUrl = "/" + moduleIconUrl;
124+
}
125+
126+
var basePath = new PathString($"/modules/$({module.Id})");
127+
var iconUrlPath = new PathString(moduleIconUrl);
128+
129+
if (!iconUrlPath.StartsWithSegments(basePath, out var subPath) ||
130+
string.IsNullOrEmpty(subPath.Value) ||
131+
!Directory.Exists(module.FullPhysicalPath))
132+
{
133+
return false;
134+
}
135+
136+
using var fileProvider = new PhysicalFileProvider(module.FullPhysicalPath);
137+
138+
return fileProvider.GetFileInfo(subPath.Value).Exists;
98139
}
99140

100141
/// <summary>
@@ -167,66 +208,97 @@ public async Task<ActionResult<ModuleDescriptor>> UploadModuleArchive()
167208
return BadRequest($"Expected a multipart request, but got {Request.ContentType}");
168209
}
169210

170-
var uploadPath = Path.GetFullPath(_platformOptions.LocalUploadFolderPath);
171-
if (!Directory.Exists(uploadPath))
211+
var targetFilePath = await UploadFile(Request, Path.GetFullPath(_platformOptions.LocalUploadFolderPath));
212+
if (targetFilePath is null)
213+
{
214+
return BadRequest("Cannot read file");
215+
}
216+
217+
var manifest = await LoadModuleManifestFromZipArchive(targetFilePath);
218+
if (manifest is null)
172219
{
173-
Directory.CreateDirectory(uploadPath);
220+
return BadRequest("Cannot read module manifest");
174221
}
175222

176-
ModuleDescriptor result = null;
177-
string targetFilePath = null;
178-
var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);
179-
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
223+
var module = AbstractTypeFactory<ManifestModuleInfo>.TryCreateInstance();
224+
module.LoadFromManifest(manifest);
225+
var existingModule = _externalModuleCatalog.Modules.OfType<ManifestModuleInfo>().FirstOrDefault(x => x.Equals(module));
226+
227+
if (existingModule != null)
228+
{
229+
module = existingModule;
230+
}
231+
else
232+
{
233+
//Force dependency validation for new module
234+
_externalModuleCatalog.CompleteListWithDependencies([module]).ToList().Clear();
235+
_externalModuleCatalog.AddModule(module);
236+
}
237+
238+
module.Ref = targetFilePath;
239+
var result = new ModuleDescriptor(module);
240+
241+
return Ok(result);
242+
}
243+
244+
private static async Task<string> UploadFile(HttpRequest request, string uploadFolderPath)
245+
{
246+
var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);
247+
var reader = new MultipartReader(boundary, request.Body);
180248
var section = await reader.ReadNextSectionAsync();
181249

182-
if (section != null)
250+
if (section == null)
183251
{
184-
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
252+
return null;
253+
}
185254

186-
if (hasContentDispositionHeader)
187-
{
188-
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
189-
{
190-
var fileName = contentDisposition.FileName.Value;
191-
targetFilePath = Path.Combine(uploadPath, fileName);
255+
if (!ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition) ||
256+
!MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
257+
{
258+
return null;
259+
}
192260

193-
using (var targetStream = System.IO.File.Create(targetFilePath))
194-
{
195-
await section.Body.CopyToAsync(targetStream);
196-
}
261+
var fileName = Path.GetFileName(contentDisposition.FileName.Value);
262+
if (string.IsNullOrEmpty(fileName))
263+
{
264+
return null;
265+
}
197266

198-
}
199-
}
200-
using (var packageStream = System.IO.File.Open(targetFilePath, FileMode.Open))
201-
using (var package = new ZipArchive(packageStream, ZipArchiveMode.Read))
267+
if (!Directory.Exists(uploadFolderPath))
268+
{
269+
Directory.CreateDirectory(uploadFolderPath);
270+
}
271+
272+
var targetFilePath = Path.Combine(uploadFolderPath, fileName);
273+
274+
await using var targetStream = System.IO.File.Create(targetFilePath);
275+
await section.Body.CopyToAsync(targetStream);
276+
277+
return targetFilePath;
278+
}
279+
280+
private static async Task<ModuleManifest> LoadModuleManifestFromZipArchive(string path)
281+
{
282+
ModuleManifest manifest = null;
283+
284+
try
285+
{
286+
await using var packageStream = System.IO.File.Open(path, FileMode.Open);
287+
using var package = new ZipArchive(packageStream, ZipArchiveMode.Read);
288+
289+
var entry = package.GetEntry("module.manifest");
290+
if (entry != null)
202291
{
203-
var entry = package.GetEntry("module.manifest");
204-
if (entry != null)
205-
{
206-
using (var manifestStream = entry.Open())
207-
{
208-
var manifest = ManifestReader.Read(manifestStream);
209-
var module = AbstractTypeFactory<ManifestModuleInfo>.TryCreateInstance();
210-
module.LoadFromManifest(manifest);
211-
var alreadyExistModule = _externalModuleCatalog.Modules.OfType<ManifestModuleInfo>().FirstOrDefault(x => x.Equals(module));
212-
if (alreadyExistModule != null)
213-
{
214-
module = alreadyExistModule;
215-
}
216-
else
217-
{
218-
//Force dependency validation for new module
219-
_externalModuleCatalog.CompleteListWithDependencies(new[] { module }).ToList().Clear();
220-
_externalModuleCatalog.AddModule(module);
221-
}
222-
module.Ref = targetFilePath;
223-
result = new ModuleDescriptor(module);
224-
}
225-
}
292+
await using var manifestStream = entry.Open();
293+
manifest = ManifestReader.Read(manifestStream);
226294
}
227295
}
296+
catch
297+
{
298+
// Suppress any exceptions
299+
}
228300

229-
return Ok(result);
301+
return manifest;
230302
}
231303

232304
/// <summary>

src/VirtoCommerce.Platform.Web/Startup.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -507,13 +507,13 @@ public void ConfigureServices(IServiceCollection services)
507507
var healthBuilder = services.AddHealthChecks()
508508
.AddCheck<ModulesHealthChecker>("Modules health",
509509
failureStatus: HealthStatus.Unhealthy,
510-
tags: new[] { "Modules" })
510+
tags: ["Modules"])
511511
.AddCheck<CacheHealthChecker>("Cache health",
512512
failureStatus: HealthStatus.Degraded,
513-
tags: new[] { "Cache" })
513+
tags: ["Cache"])
514514
.AddCheck<RedisHealthCheck>("Redis health",
515515
failureStatus: HealthStatus.Unhealthy,
516-
tags: new[] { "Cache" });
516+
tags: ["Cache"]);
517517

518518
var connectionString = Configuration.GetConnectionString("VirtoCommerce");
519519
switch (databaseProvider)
@@ -522,19 +522,19 @@ public void ConfigureServices(IServiceCollection services)
522522
healthBuilder.AddMySql(connectionString,
523523
name: "MySql health",
524524
failureStatus: HealthStatus.Unhealthy,
525-
tags: new[] { "Database" });
525+
tags: ["Database"]);
526526
break;
527527
case "PostgreSql":
528528
healthBuilder.AddNpgSql(connectionString,
529529
name: "PostgreSql health",
530530
failureStatus: HealthStatus.Unhealthy,
531-
tags: new[] { "Database" });
531+
tags: ["Database"]);
532532
break;
533533
default:
534534
healthBuilder.AddSqlServer(connectionString,
535535
name: "SQL Server health",
536536
failureStatus: HealthStatus.Unhealthy,
537-
tags: new[] { "Database" });
537+
tags: ["Database"]);
538538
break;
539539
}
540540

src/VirtoCommerce.Platform.Web/Swagger/CustomSwaggerGenerator.cs

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Diagnostics;
33
using System.Reflection;
4+
using System.Threading.Tasks;
45
using Microsoft.AspNetCore.Mvc.ApiExplorer;
56
using Microsoft.OpenApi.Models;
67
using Swashbuckle.AspNetCore.Swagger;
@@ -12,7 +13,7 @@ namespace VirtoCommerce.Platform.Web.Swagger
1213
/// <summary>
1314
/// Generate swagger schema ids for schema refs depending on document name
1415
/// </summary>
15-
public class CustomSwaggerGenerator : ISwaggerProvider
16+
public class CustomSwaggerGenerator : IAsyncSwaggerProvider
1617
{
1718
private readonly SwaggerGenerator _swaggerGenerator;
1819
private readonly SchemaGeneratorOptions _schemaGeneratorOptions;
@@ -30,10 +31,10 @@ public CustomSwaggerGenerator(SwaggerGeneratorOptions options, IApiDescriptionGr
3031
_swaggerGenerator = new SwaggerGenerator(options, apiDescriptionsProvider, schemaGenerator);
3132
}
3233

33-
public OpenApiDocument GetSwagger(string documentName, string host = null, string basePath = null)
34+
public Task<OpenApiDocument> GetSwaggerAsync(string documentName, string host = null, string basePath = null)
3435
{
3536
SetSchemaIdSelector(documentName);
36-
return _swaggerGenerator.GetSwagger(documentName, host, basePath);
37+
return _swaggerGenerator.GetSwaggerAsync(documentName, host, basePath);
3738
}
3839

3940
private void SetSchemaIdSelector(string documentName)
@@ -46,13 +47,13 @@ private void SetSchemaIdSelector(string documentName)
4647
{
4748
result = attribute.Id;
4849
}
49-
else if (documentName == SwaggerServiceCollectionExtensions.platformUIDocName)
50+
else if (documentName == SwaggerServiceCollectionExtensions.PlatformUIDocName)
5051
{
5152
result = type.FullName;
5253
}
5354
else
5455
{
55-
result = (string)_defaultSchemaIdSelectorMethodInfo.Invoke(_schemaGeneratorOptions, new object[] { type });
56+
result = (string)_defaultSchemaIdSelectorMethodInfo.Invoke(_schemaGeneratorOptions, [type]);
5657
}
5758
Trace.WriteLine($"SchemaIdSelector: {type.FullName} => {result}");
5859
return result;

0 commit comments

Comments
 (0)