Filter products by category in a Shopware 6 Import/Export Profile - shopware6

I created a Profile in shopware 6 with the api.
The body of the request is:
{
"id": "my99id2cb2784069938089a8a77ctest",
"label": "Label test",
"name": "name test",
"sourceEntity": "product",
"fileType": "text/csv",
"delimiter": ";",
"enclosure": "\"",
"createdAt": "2022-11-10T10:15:22Z",
"mapping": [{"key":"id","mappedKey":"id","position":0,"id":"someid4f7c6c478b8041cfd5fe0ae0c5"},{"key":"cover.media.url","mappedKey":"cover","position":1,"id":"someid0ffa7f48e6b135f6e754d6b93c"}],
"config": {"createEntities": false, "updateEntities": true}
}
Is there a way to specify that i want only product from certain categories? Not all of them

No, this currently isn't possible with just the profile and existing endpoints. You'll have to implement a custom api endpoint.
If you are able to do that follow the steps from this answer until you first retrieve a logId. Then instead of starting the process using the existing endpoint, request your new custom endpoint. The implementation could look similar to this:
/**
* #Route(defaults={"_routeScope"={"api"}})
*/
class CustomExportApiController extends AbstractController
{
private ImportExportFactory $importExportFactory;
public function __construct(ImportExportFactory $importExportFactory)
{
$this->importExportFactory = $importExportFactory;
}
/**
* #Route("/api/_action/custom/export/{logId}/{categoryId}", name="api.action.custom.export", methods={"POST"})
*/
public function customProductExport(string $logId, string $categoryId, Context $context): JsonResponse
{
$importExport = $this->importExportFactory->create($logId, 50, 50);
$logEntity = $importExport->getLogEntity();
if ($logEntity->getState() === Progress::STATE_ABORTED) {
return new JsonResponse(['success' => false]);
}
$criteria = new Criteria();
$criteria->addFilter(new EqualsAnyFilter('categoryTree', [$categoryId]));
$offset = 0;
do {
$progress = $importExport->export($context, $criteria, $offset);
$offset = $progress->getOffset();
} while (!$progress->isFinished());
return new JsonResponse(['success' => true]);
}
}
After calling your custom endpoint proceed with the steps as described in the answer linked above.

Related

Can't serialize GeoJson coordinates in .Net Core controller

I have a .net 6 API project that exports FeatureCollections. I'm using 'NetTopologySuite.IO.GeoJSON' version 2.0.4, and have a public API call like:
public async Task<IActionResult> ExportGeoJSON()
{
GeoJSON.Net.Feature.FeatureCollection ret = new GeoJSON.Net.Feature.FeatureCollection();
const double lat = -73.697913;
const double lon = 50.659193;
GeoJSON.Net.Geometry.Position coord = new GeoJSON.Net.Geometry.Position(lat, lon);
GeoJSON.Net.Geometry.Point pt = new GeoJSON.Net.Geometry.Point(coord);
GeoJSON.Net.Feature.Feature feat = new GeoJSON.Net.Feature.Feature(pt, null, Guid.NewGuid().ToString());
ret.Features.Add(feat);
return Ok(ret);
}
When i call this, i get back only:
{
"Type": "FeatureCollection",
"Features": [
{
"Type": "Feature",
"Id": "465f399d-b45c-47ed-b9e6-f395cd86b84b",
"Geometry": {
"Type": "Point"
}
},...
Ok, i look around and find out about GeoJSON4STJ https://github.com/NetTopologySuite/NetTopologySuite.IO.GeoJSON , so I put that into my Startup:
services.AddControllers()
.AddJsonOptions(opts =>
{
opts.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
opts.JsonSerializerOptions.PropertyNamingPolicy = null;
opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
opts.JsonSerializerOptions.Converters.Add(new NetTopologySuite.IO.Converters.GeoJsonConverterFactory());
});
and run it again, but no change at all. Exactly the same response. Am i missing something?
It seems that you are mixing libraries. NetTopologySuite.IO.GeoJSON (and NetTopologySuite.IO.GeoJSON4STJ) works with features from NetTopologySuite.Features package not from GeoJSON.Net.

Is it possible to expose the same Swagger JSON in both Swagger 2.0 and Open API 3 formats with Swashbuckle in ASP .NET Core?

We have a legacy application that only works with Swagger 2.0 JSON format. For everything else we would like to use Open API format.
Is there any way with Swashbuckle .NET Core to expose JSON in different formats under separate URLs? It looks like the SerializeAsV2 property in the UseSwagger method options is global for all endpoints.
Basically I would like to have the following end points that contain the same API data in different formats.
/swagger/v1/openapi/swagger.json
/swagger/v1/swagger2/swagger.json
An alternative approach is to split the request pipeline:
// serve v3 from /swagger/v1/swagger.json
app.UseSwagger(o => o.RouteTemplate = "swagger/{documentName}/swagger.json");
// serve v2 from /swagger2/v1/swagger.json
app.Map("/swagger2", swaggerApp =>
swaggerApp.UseSwagger(options => {
// note the dropped prefix "swagger/"
options.RouteTemplate = "{documentName}/swagger.json";
options.SerializeAsV2 = true;
})
);
You can serialize the OpenAPI document as V2 and serve it yourself. Taking SwaggerMiddleware as reference:
First register SwaggerGenerator:
services.AddTransient<SwaggerGenerator>();
Then inject SwaggerGenerator and build the document. Serve it from an endpoint or a controller. You can take the version as a path parameter to decide which version to serve.
app.UseEndpoints(e =>
{
// ...
e.MapGet("/swagger/{documentName}/swagger.{version}.json", context =>
{
var documentName = context.Request.RouteValues.GetValueOrDefault("documentName", null) as string;
var version = context.Request.RouteValues.GetValueOrDefault("version", null) as string;
if (documentName is null || version is null)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
return Task.CompletedTask;
}
// inject SwaggerGenerator
var swaggerGenerator = context.RequestServices.GetRequiredService<SwaggerGenerator>();
var doc = swaggerGenerator.GetSwagger(documentName);
// serialize the document as json
using var writer = new StringWriter(CultureInfo.InvariantCulture);
var serializer = new OpenApiJsonWriter(writer);
if (version == "v2")
{
doc.SerializeAsV2(serializer);
}
else
{
doc.SerializeAsV3(serializer);
}
var json = writer.ToString();
// serve it as json
context.Response.ContentType = MediaTypeNames.Application.Json;
return context.Response.WriteAsync(json, new UTF8Encoding(false));
});
});
When you visit /swagger/v1/openapi.v2.json, you'll get the OpenAPI doc serialized as v2.
{
"swagger": "2.0",
"info": {
"title": "ApiPlayground",
"version": "1.0"
},
"paths": { ... }
}
Whereas /swagger/v1/openapi.v3.json will give you the doc serialized as v3:
{
"openapi": "3.0.1",
"info": {
"title": "ApiPlayground",
"version": "1.0"
},
"paths": { ... },
"components": { ... }
}

Load dynamic SAML schemes for IdentityServer4 using ComponentSpace

We have centralized a IdentityServer4 that will act as service provider and there are multiple identity providers like Active Directory, Google, Facebook and also other SAML providers based on each tenant. i.e., one service provider and multiple identity providers.
To load the openId configs from database I am exactly following the https://stackoverflow.com/a/56941908/2922388 and it is working as expected for openid and now I need to integrate SAML providers in the same way.
I went through the "SAMLv20.Core-evaluation" from componentspace and was able to do the integration using appsettings.json successfully.
But I am not sure how to integrate it programatically as given in https://stackoverflow.com/a/56941908/2922388.
Here is what I have done so far
public class AccountController : ControllerBase
{
private readonly IOptionsMonitorCache<OpenIdConnectOptions> _openIdOptionsCache;
private readonly IOptionsMonitorCache<SamlAuthenticationOptions> _samlOptionsCache;
private readonly OpenIdConnectPostConfigureOptions _postConfigureOptions;
private readonly SamlPostConfigureAuthenticationOptions _samlPostConfigureOptions;
public AccountController(
IOptionsMonitorCache<OpenIdConnectOptions> openidOptionsCache,
IOptionsMonitorCache<SamlAuthenticationOptions> samlOptionsCache,
OpenIdConnectPostConfigureOptions postConfigureOptions,
SamlPostConfigureAuthenticationOptions samlPostConfigureOptions
)
{
_openIdOptionsCache = openidOptionsCache;
_samlOptionsCache = samlOptionsCache;
_postConfigureOptions = postConfigureOptions;
_samlPostConfigureOptions = samlPostConfigureOptions;
}
private async Task<IEnumerable<AuthenticationScheme>> LoadAuthenticationSchemesByTenant(IEnumerable<AuthenticationScheme> schemes, AuthProviderSetting tenantAuthProviderSetting)
{
dynamic configJson = JsonConvert.DeserializeObject(tenantAuthProviderSetting.tenantConfigJson);
switch (tenantAuthProviderSetting.AuthenticationType)
{
case AuthenticationTypes.OpenID:
var oidcOptions = new OpenIdConnectOptions
{
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
SignOutScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
SaveTokens = true,
Authority = configJson.Authority,
ClientId = configJson.ClientId,
ClientSecret = configJson.ClientSecret,
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role",
ValidateIssuer = false
}
};
_schemeProvider.AddScheme(new AuthenticationScheme(tenantAuthProviderSetting.AuthenticationScheme, tenantAuthProviderSetting.DisplayName, typeof(OpenIdConnectHandler)));
_postConfigureOptions.PostConfigure(tenantAuthProviderSetting.AuthenticationScheme, oidcOptions);
_openIdOptionsCache.TryAdd(tenantAuthProviderSetting.AuthenticationScheme, oidcOptions);
schemes = await _schemeProvider.GetAllSchemesAsync();
break;
case AuthenticationTypes.SAML:
var samlOptions = new SamlAuthenticationOptions
{
PartnerName = delegate () { return "https://ExampleIdentityProvider"; },
SingleLogoutServicePath = "https://localhost:44313/SAML/SingleLogoutService",
// Not sure how to set other parameters here
};
_schemeProvider.AddScheme(new AuthenticationScheme(tenantAuthProviderSetting.AuthenticationScheme, tenantAuthProviderSetting.DisplayName, typeof(SamlAuthenticationHandler)));
_samlPostConfigureOptions.PostConfigure(tenantAuthProviderSetting.AuthenticationScheme, samlOptions);
_samlOptionsCache.TryAdd(tenantAuthProviderSetting.AuthenticationScheme, samlOptions);
schemes = await _schemeProvider.GetAllSchemesAsync();
break;
default:
schemes = await _schemeProvider.GetAllSchemesAsync();
break;
}
return schemes;
}
}
Here is the config through which I used to statically integrate with IdentityServer
"SAML": {
"$schema": "https://www.componentspace.com/schemas/saml-config-schema-v1.0.json",
"Configurations": [
{
"LocalServiceProviderConfiguration": {
"Name": "https://IdentityServer4",
"Description": "IdentityServer4",
"AssertionConsumerServiceUrl": "http://localhost:44380/SAML/AssertionConsumerService",
"SingleLogoutServiceUrl": "http://localhost:44380/SAML/SingleLogoutService",
"LocalCertificates": [
{
"FileName": "certificates/sp.pfx",
"Password": "password"
}
]
},
"PartnerIdentityProviderConfigurations": [
{
"Name": "https://ExampleIdentityProvider",
"Description": "Example Identity Provider",
"SignAuthnRequest": true,
"SingleSignOnServiceUrl": "https://localhost:44313/SAML/SingleSignOnService",
"SingleLogoutServiceUrl": "https://localhost:44313/SAML/SingleLogoutService",
"PartnerCertificates": [
{
"FileName": "certificates/idp.cer"
}
]
}
]
}
]
},
"PartnerName": "https://ExampleIdentityProvider"
Just to confirm, you want to add the SAML configuration dynamically?
The best way to do this is to implement the ISamlConfigurationResolver as described in the "Implementing ISamlConfigurationResolver" section of our Configuration Guide.
https://www.componentspace.com/Forums/8234/Configuration-Guide
Your implementation of ISamlConfigurationResolver is called whenever configuration is required. This means the SAML configuration is entirely dynamic.

Health Checks UI display results in categories

I have implemented Xabaril's Healthchecks in a simple .NET Core 3.0 API project. I am checking several URLs and SQL servers as a test and I successfully display the results in the HealthCheckUI. They all appear under the one 'category' that I defined in appSettings. Now, in the documentation you can see that you could have multiple of these categories, but it seems that this is meant to be used only when you display results from different sources.
What I want to do is have my API project check say 3 URIs and display them under a "Web" category, and then check 3 SQL servers and display them under a "SQL" category.
From this example here it seems that we can achieve the 2 different categories with this piece of code:
app
.UseHealthChecks("/health", new HealthCheckOptions
{
Predicate = _ => true
})
.UseHealthChecks("/healthz", new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
})
However in the Startup class, when we add checks we do not specify to which API endpoint they should be reported to so that we can categorize them:
services.AddHealthChecks().AddCheck...
Am I missing something or this UI client is not meant to be used like this?
I got this working in a Net Core 3.1 project using tags.
For example if you wanted a Database and API section in the Spa UI:
In ConfigureServices method of Startup.cs
services.AddHealthChecks()
.AddSqlServer("ConnectionString", name: "Application Db", tags: new[] { "database" })
.AddHangfire(options => { options.MinimumAvailableServers = 1; }, "Hangfire Db", tags: new[] { "database" })
.AddUrlGroup(new Uri("https://myapi.com/api/person"), HttpMethod.Get, "Person Endpoint", tags: new[] { "api" })
.AddUrlGroup(new Uri("https://myapi.com/api/organisation"), HttpMethod.Get, "Org Endpoint", tags: new[] { "api" })
.AddUrlGroup(new Uri("https://myapi.com/api/address"), HttpMethod.Get, "Address Endpoint", tags: new[] { "api" });
In Configure method of Startup.cs
config.MapHealthChecks("/health-check-database", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("database"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
config.MapHealthChecks("/health-check-api", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("api"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
Then appsettings.json something like:
"HealthChecksUI": {
"HealthChecks": [
{
"Name": "Database Health Checks",
"Uri": "https://myapi.com/api/health-check-database"
},
{
"Name": "API Health Checks",
"Uri": "https://myapi.com/api/health-check-api"
}
],
"Webhooks": [],
"EvaluationTimeinSeconds": 10,
"MinimumSecondsBetweenFailureNotifications": 60
}

RavenDB Patch API: updating a nested collection

I am trying to update a nested collection using the Patch API. More specifically, consider the following example - a Posts collection:
{
"Title": "Hello RavenDB",
"Category": "RavenDB",
"Content": "This is a blog about RavenDB",
"Comments": [
{
"Title": "Unrealistic",
"Content": "This example is unrealistic"
},
{
"Title": "Nice",
"Content": "This example is nice"
}
]
}
I used the Patch API and Set-based operation docs at http://ravendb.net/docs/client-api/partial-document-updates and http://ravendb.net/docs/client-api/set-based-operations as well as several stackoverflow questions as resources to do a bulk update using set operations and a static index. A requirement is to update the "Title" of a comment only when the previous value was "Nice" and if so, update it to "Bad".
The static index "NicePosts" is defined as:
Map = posts => from post in posts
where post.Comments.Any(comment => comment.Title == "Nice")
select new {post.Title, post.Category}
The bulk patch update command is:
documentStore.DatabaseCommands.UpdateByIndex("NicePosts",
new IndexQuery(),
new[] { new PatchRequest
{ Type = PatchCommandType.Modify,
Name = "Comments",
PrevVal = RavenJObject.Parse(#"{ ""Title"": ""Nice""}"),
Nested = new[]
{
new PatchRequest {Type = PatchCommandType.Set, Name = "Title", Value = new RavenJValue("Bad") },
} }, allowStale: true);
I have some questions regarding this:
1) Is my structure/syntax for the update command correct?
2) I would like the update to be performed on all the records in the collection. Hence I haven't defined the query filter in the IndexQuery Query because the "NicePosts" index already returns the appropriate set. However running this command doesn't update the collection.
3) If I set "allowStale:false" I get a "stale index" error. Before opening my document store session I instantiate the index class and Execute it to persist it to the ravenDB instance. Any ideas whats going wrong here?
Thanks,
EDIT:
Based on ayende's recommendation changed Patch command to:
documentStore.DatabaseCommands.UpdateByIndex("NicePosts",
new IndexQuery(),
new[] {
new PatchRequest {
Type = PatchCommandType.Modify,
Name = "Comments",
Position = 0,
Nested = new[] {
new PatchRequest {Type = PatchCommandType.Set, Name = "Title", Value = new RavenJValue("Bad")},
}
}
}, allowStale: false);
This can now be done using the scripted patch request:
string oldTitle = "Nice";
string newTitle = "Bad";
documentStore.DatabaseCommands.UpdateByIndex("NicePosts",
new IndexQuery(),
new ScriptedPatchRequest
{
Script = #"for (var i = 0; i < this.Comments.length; i++)
if (this.Comments[i].Title == oldTitle)
this.Comments[i].Title = newTitle;",
Values =
{
{ "oldTitle", oldTitle },
{ "newTitle", newTitle },
},
}
);
You can't use the patch command to update values based on the existing value in the array.
You need to specify the actual position.