Repository: VirtoCommerce/vc-storefront
Branch: dev
Commit: 5242024b5236
Files: 451
Total size: 4.0 MB
Directory structure:
gitextract_go6x876s/
├── .deployment/
│ └── storefront-app/
│ └── argoDeploy.json
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ ├── pull_request_template.md
│ └── workflows/
│ ├── deploy.yml
│ ├── main.yml
│ ├── msteams.yml
│ ├── pr-ci.yml
│ ├── pr-deploy.yml
│ ├── release-branch.yml
│ └── release.yml
├── .gitignore
├── .nuke
├── CONTRIBUTING.md
├── Directory.Build.props
├── LICENSE
├── README.md
├── VirtoCommerce.LiquidThemeEngine/
│ ├── Converters/
│ │ └── CountryConverter.cs
│ ├── Exceptions/
│ │ └── SaasCompileException.cs
│ ├── Extensions/
│ │ ├── GraphQLResponseExtensions.cs
│ │ ├── StringExtensions.cs
│ │ └── UriExtensions.cs
│ ├── Filters/
│ │ ├── ArrayFilters.cs
│ │ ├── CommerceFilters.cs
│ │ ├── CommonFilters.cs
│ │ ├── DynamicDataSourceFilters.cs
│ │ ├── FeatureFilter.cs
│ │ ├── HtmlFilters.cs
│ │ ├── MathFilters.cs
│ │ ├── MoneyFilters.cs
│ │ ├── StandardFilters.cs
│ │ ├── StringFilters.cs
│ │ ├── TranslationFilter.cs
│ │ └── UrlFilters.cs
│ ├── ILiquidThemeEngine.cs
│ ├── ILiquidViewEngine.cs
│ ├── ISassFileManager.cs
│ ├── JsonConverters/
│ │ └── MutablePagedListAsArrayJsonConverter.cs
│ ├── LiquidThemeEngineOptions.cs
│ ├── LiquidThemedView.cs
│ ├── LiquidThemedViewEngine.cs
│ ├── Objects/
│ │ ├── Paginate.cs
│ │ └── Part.cs
│ ├── SassFileManager.cs
│ ├── Scriban/
│ │ └── WorkContextConverter.cs
│ ├── SettingsManager.cs
│ ├── ShopifyLiquidThemeEngine.cs
│ ├── ThemeEngineCacheRegion.cs
│ └── VirtoCommerce.LiquidThemeEngine.csproj
├── VirtoCommerce.Storefront/
│ ├── .bowerrc
│ ├── AutoRestClients/
│ │ ├── !readme.txt
│ │ ├── CatalogModuleApi.cs
│ │ ├── ContentModuleApi.cs
│ │ ├── CoreModuleApi.cs
│ │ ├── CustomerModuleApi.cs
│ │ ├── NotificationsModuleApi.cs
│ │ ├── PlatformModuleApi.cs
│ │ ├── SitemapsModuleApi.cs
│ │ ├── StoreModuleApi.cs
│ │ └── array-in-query-fix.yml
│ ├── Caching/
│ │ ├── Redis/
│ │ │ ├── RedisCachingMessage.cs
│ │ │ ├── RedisCachingOptions.cs
│ │ │ └── RedisStorefrontMemoryCache.cs
│ │ ├── ServiceCollectionExtensions.cs
│ │ └── StorefrontMemoryCache.cs
│ ├── Connected Services/
│ │ └── Application Insights/
│ │ └── ConnectedService.json
│ ├── Controllers/
│ │ ├── AccountController.cs
│ │ ├── Api/
│ │ │ ├── ApiAccountController.cs
│ │ │ ├── ApiBlogController.cs
│ │ │ ├── ApiCommonController.cs
│ │ │ ├── ApiStaticContentController.cs
│ │ │ └── ApiThemeController.cs
│ │ ├── AssetController.cs
│ │ ├── CommonController.cs
│ │ ├── DesignerPreviewController.cs
│ │ ├── ErrorController.cs
│ │ ├── HomeController.cs
│ │ ├── SitemapController.cs
│ │ └── StorefrontControllerBase.cs
│ ├── DependencyInjection/
│ │ └── ServiceCollectionExtension.cs
│ ├── Domain/
│ │ ├── Common/
│ │ │ ├── AddressConverter.cs
│ │ │ ├── ContactUsFormConverter.cs
│ │ │ ├── CurrencyConverter.cs
│ │ │ ├── DynamicPropertyConverter.cs
│ │ │ ├── NotificationConverter.cs
│ │ │ ├── SeoInfoConverter.cs
│ │ │ ├── SettingConverter.cs
│ │ │ └── ToolsConverter.cs
│ │ ├── ContentBlobProviders/
│ │ │ ├── AzureBlobContentOptions.cs
│ │ │ ├── AzureBlobContentProvider.cs
│ │ │ ├── BlobConnectionString.cs
│ │ │ ├── ContentBlobCacheRegion.cs
│ │ │ ├── FileSystemBlobContentOptions.cs
│ │ │ └── FileSystemContentBlobProvider.cs
│ │ ├── Countries/
│ │ │ ├── CountriesWorkContextBuilderExtensions.cs
│ │ │ ├── FileSystemCountriesOptions.cs
│ │ │ └── FileSystemCountriesService.cs
│ │ ├── CurrencyService.cs
│ │ ├── Customer/
│ │ │ ├── CutomerCacheRegion.cs
│ │ │ ├── Handlers/
│ │ │ │ └── SecurityEventsHandler.cs
│ │ │ ├── MemberConverter.cs
│ │ │ └── MemberService.cs
│ │ ├── IWorkContextBuilder.cs
│ │ ├── Security/
│ │ │ ├── AnonymousUserForStoreAuthorizationRequirement.cs
│ │ │ ├── CanEditOrganizationResourceAuthorizationHandler.cs
│ │ │ ├── CanImpersonateAuthorizationHandler.cs
│ │ │ ├── CanReadContentItemAuthorizationHandler.cs
│ │ │ ├── CustomCookieAuthenticationEvents.cs
│ │ │ ├── CustomSignInManager.cs
│ │ │ ├── CustomUserManager.cs
│ │ │ ├── Notifications/
│ │ │ │ ├── ChangePhoneNumberSmsNotification.cs
│ │ │ │ ├── EmailConfirmationNotification.cs
│ │ │ │ ├── RegistrationEmailNotification.cs
│ │ │ │ ├── RegistrationInvitationNotification.cs
│ │ │ │ ├── RemindUserNameNotification.cs
│ │ │ │ ├── ResetPasswordEmailNotification.cs
│ │ │ │ ├── ResetPasswordSmsNotification.cs
│ │ │ │ ├── TwoFactorEmailNotification.cs
│ │ │ │ └── TwoFactorSmsNotification.cs
│ │ │ ├── OnlyRegisteredUserAuthorizationHandler.cs
│ │ │ ├── PermissionAuthorizationHandler.cs
│ │ │ ├── PermissionAuthorizationPolicyProvider.cs
│ │ │ ├── PermissionAuthorizationRequirement.cs
│ │ │ ├── PollingApiUserChangeToken.cs
│ │ │ ├── SecurityCacheRegion.cs
│ │ │ ├── SecurityConverter.cs
│ │ │ ├── SecurityWorkContextBuilderExtensions.cs
│ │ │ └── UserStoreStub.cs
│ │ ├── SeoInfoService.cs
│ │ ├── SpaRouteService.cs
│ │ ├── StaticContent/
│ │ │ ├── LinkListConverter.cs
│ │ │ ├── LinkListServiceImpl.cs
│ │ │ ├── MarkdownContentLoader.cs
│ │ │ ├── PageBuilderContentLoader.cs
│ │ │ ├── StaticContentCacheRegion.cs
│ │ │ ├── StaticContentItemFactory.cs
│ │ │ ├── StaticContentLoader.cs
│ │ │ ├── StaticContentLoaderFactory.cs
│ │ │ ├── StaticContentService.cs
│ │ │ └── StaticContentWorkContextBuilderExtensions.cs
│ │ ├── Stores/
│ │ │ ├── SelectCurrentCurrencyPolicy.cs
│ │ │ ├── SelectCurrentLanguagePolicy.cs
│ │ │ ├── SelectCurrentStorePolicy.cs
│ │ │ ├── StoreCacheRegion.cs
│ │ │ ├── StoreConverter.cs
│ │ │ ├── StoreService.cs
│ │ │ └── StoreWorkContextBuilderExtensions.cs
│ │ ├── WorkContextAccessor.cs
│ │ └── WorkContextBuilder.cs
│ ├── Extensions/
│ │ ├── ClaimsPrincipalExtensions.cs
│ │ ├── HostingEnviromentExtension.cs
│ │ ├── ObjectExtensions.cs
│ │ ├── PathStringExtensions.cs
│ │ ├── QueryCollectionExtensions.cs
│ │ └── SeoExtensions.cs
│ ├── Filters/
│ │ ├── AngularAntiforgeryCookieResultFilterAttribute.cs
│ │ └── AnonymousUserForStoreAuthorizationFilter.cs
│ ├── IISUrlRewrite.xml
│ ├── Infrastructure/
│ │ ├── ApiAuthMode.cs
│ │ ├── ApiChangesWatcher.cs
│ │ ├── ApplicationInsights/
│ │ │ ├── ServiceCollectionExtension.cs
│ │ │ ├── SnapshotCollectorTelemetryProcessorFactory.cs
│ │ │ └── UserTelemetryInitializer.cs
│ │ ├── Autorest/
│ │ │ ├── ApiKeySecretAuthHandler.cs
│ │ │ ├── AuthenticationHandlerFactory.cs
│ │ │ ├── BaseAuthHandler.cs
│ │ │ ├── ClientCredentialsAuthHandler.cs
│ │ │ ├── EmptyServiceClientCredentials.cs
│ │ │ └── UserPasswordAuthHandler.cs
│ │ ├── BlobChangeToken.cs
│ │ ├── BlobChangesWatcher.cs
│ │ ├── Exceptions/
│ │ │ ├── NoStoresException.cs
│ │ │ └── NoThemeException.cs
│ │ ├── HealthCheck/
│ │ │ └── PlatformConnectionHealthChecker.cs
│ │ ├── HmacUtility.cs
│ │ ├── IApiChangesWatcher.cs
│ │ ├── IBlobChangesWatcher.cs
│ │ ├── PlatformEndpointOptions.cs
│ │ ├── PollingApiChangeToken.cs
│ │ ├── RequireHttpsOptions.cs
│ │ ├── StorefrontOptions.cs
│ │ ├── StorefrontUrlBuilder.cs
│ │ └── Swagger/
│ │ ├── ApiExplorerApiControllersConvention.cs
│ │ ├── ArrayInQueryParametersFilter.cs
│ │ ├── AuthResponsesOperationFilter.cs
│ │ ├── ConsumeFromBodyFilter.cs
│ │ ├── EnumSchemaFilter.cs
│ │ ├── FileResponseTypeFilter.cs
│ │ ├── FileUploadOperationFilter.cs
│ │ ├── NewtonsoftJsonIgnoreFilter.cs
│ │ ├── OptionalParametersFilter.cs
│ │ ├── SwaggerFileResponseAttribute.cs
│ │ ├── SwaggerOptionalAttribute.cs
│ │ └── UploadFileAttribute.cs
│ ├── Middleware/
│ │ ├── NoLiquidThemeMiddleware.cs
│ │ ├── StoreMaintenanceMiddleware.cs
│ │ └── WorkContextBuildMiddleware.cs
│ ├── Models/
│ │ ├── NoThemeViewModel.cs
│ │ └── ResetCacheModel.cs
│ ├── Program.cs
│ ├── Properties/
│ │ └── launchSettings.json
│ ├── Routing/
│ │ ├── ISlugRouteService.cs
│ │ ├── MapStorefrontRouteBuilderExtension.cs
│ │ ├── RoutingCacheRegion.cs
│ │ ├── SlugRoute.cs
│ │ ├── SlugRouteResponse.cs
│ │ ├── SlugRouteService.cs
│ │ ├── StorefrontApiRouteAttribute.cs
│ │ ├── StorefrontRoute.cs
│ │ └── StorefrontRouteAttribute.cs
│ ├── Startup.cs
│ ├── Views/
│ │ ├── Common/
│ │ │ ├── Maintenance.cshtml
│ │ │ └── NoTheme.cshtml
│ │ ├── Error/
│ │ │ ├── 404.cshtml
│ │ │ ├── 500.cshtml
│ │ │ ├── AccessDenied.cshtml
│ │ │ ├── Error.cshtml
│ │ │ └── NoStore.cshtml
│ │ ├── Shared/
│ │ │ ├── _Layout.cshtml
│ │ │ └── _ValidationScriptsPartial.cshtml
│ │ ├── _ViewImports.cshtml
│ │ └── _ViewStart.cshtml
│ ├── VirtoCommerce.Storefront.csproj
│ ├── appsettings.Development.json
│ ├── appsettings.Production.json
│ ├── appsettings.json
│ ├── bower.json
│ ├── bundleconfig.json
│ ├── bundleconfig.json.bindings
│ ├── web.config
│ └── wwwroot/
│ ├── countries.json
│ ├── css/
│ │ └── site.css
│ ├── js/
│ │ └── site.js
│ └── lib/
│ ├── bootstrap/
│ │ ├── .bower.json
│ │ ├── LICENSE
│ │ └── dist/
│ │ ├── css/
│ │ │ ├── bootstrap-theme.css
│ │ │ └── bootstrap.css
│ │ └── js/
│ │ ├── bootstrap.js
│ │ └── npm.js
│ ├── jquery/
│ │ ├── .bower.json
│ │ ├── LICENSE.txt
│ │ └── dist/
│ │ └── jquery.js
│ ├── jquery-validation/
│ │ ├── .bower.json
│ │ ├── LICENSE.md
│ │ └── dist/
│ │ ├── additional-methods.js
│ │ └── jquery.validate.js
│ └── jquery-validation-unobtrusive/
│ ├── .bower.json
│ └── jquery.validate.unobtrusive.js
├── VirtoCommerce.Storefront.Model/
│ ├── Address.cs
│ ├── AddressType.cs
│ ├── AmountType.cs
│ ├── Attachment.cs
│ ├── BankCardInfo.cs
│ ├── Caching/
│ │ └── IStorefrontMemoryCache.cs
│ ├── Common/
│ │ ├── AnonymousComparer.cs
│ │ ├── ArrayExtensions.cs
│ │ ├── AsyncLock.cs
│ │ ├── Breadcrumb.cs
│ │ ├── Bus/
│ │ │ ├── IHandlerRegistrar.cs
│ │ │ └── InProcessBus.cs
│ │ ├── Caching/
│ │ │ ├── CacheCancellableTokensRegistry.cs
│ │ │ ├── CacheKey.cs
│ │ │ ├── CancellableCacheRegion.cs
│ │ │ ├── GlobalCacheRegion.cs
│ │ │ ├── ICacheKey.cs
│ │ │ ├── MemoryCacheExtensions.cs
│ │ │ └── TokenCancelledEventArgs.cs
│ │ ├── CollectionExtensions.cs
│ │ ├── DefaultableDictionary.cs
│ │ ├── DynamicPropertyExtension.cs
│ │ ├── EmptyDisposable.cs
│ │ ├── Entity.cs
│ │ ├── EntryState.cs
│ │ ├── EnumUtility.cs
│ │ ├── EnumerableExtension.cs
│ │ ├── EventThrottlingExtensions.cs
│ │ ├── Events/
│ │ │ ├── DomainEvent.cs
│ │ │ ├── ICancellableEventHandler.cs
│ │ │ ├── IEvent.cs
│ │ │ ├── IEventHandler.cs
│ │ │ └── IEventPublisher.cs
│ │ ├── Exceptions/
│ │ │ └── StorefrontException.cs
│ │ ├── GenericSearchResult.cs
│ │ ├── GeoPoint.cs
│ │ ├── IAccessibleByIndexKey.cs
│ │ ├── IEntity.cs
│ │ ├── IHasBreadcrumbs.cs
│ │ ├── IHasQueryKeyValues.cs
│ │ ├── IMutablePagedList.cs
│ │ ├── IQueryableExtensions.cs
│ │ ├── IStorefrontUrlBuilder.cs
│ │ ├── IValueObject.cs
│ │ ├── LocalizationExtensions.cs
│ │ ├── Messages/
│ │ │ ├── ICancellableHandler.cs
│ │ │ ├── IHandler.cs
│ │ │ └── IMessage.cs
│ │ ├── Money/
│ │ │ ├── Currency.cs
│ │ │ ├── IConvertible.cs
│ │ │ └── Money.cs
│ │ ├── MutablePagedList.cs
│ │ ├── MutablePagedListExtensions.cs
│ │ ├── Notifications/
│ │ │ ├── EmailNotificationBase.cs
│ │ │ ├── NotificationBase.cs
│ │ │ └── SmsNotificationBase.cs
│ │ ├── NumericRange.cs
│ │ ├── ObjectExtensions.cs
│ │ ├── PagedSearchCriteria.cs
│ │ ├── PathUtils.cs
│ │ ├── ReflectionExtension.cs
│ │ ├── SettingsExtension.cs
│ │ ├── SortInfo.cs
│ │ ├── Specifications/
│ │ │ └── ISpecification.cs
│ │ ├── StreamExtensions.cs
│ │ ├── StringExtensions.cs
│ │ ├── TreeNode.cs
│ │ ├── TypeExtensions.cs
│ │ └── ValueObject.cs
│ ├── ContactForm.cs
│ ├── Country.cs
│ ├── CountryRegion.cs
│ ├── Customer/
│ │ ├── Contact.cs
│ │ ├── Member.cs
│ │ ├── Organization.cs
│ │ ├── OrganizationContactsSearchCriteria.cs
│ │ ├── SecurityAccount.cs
│ │ └── Services/
│ │ └── IMemberService.cs
│ ├── DynamicProperty.cs
│ ├── DynamicPropertyDictionaryItem.cs
│ ├── DynamicPropertyName.cs
│ ├── EditorialReview.cs
│ ├── Features/
│ │ ├── Exceptions/
│ │ │ └── FeaturesException.cs
│ │ ├── Feature.cs
│ │ ├── FeatureExtensions.cs
│ │ ├── FeaturesAgent.cs
│ │ └── IFeaturesAgent.cs
│ ├── Form.cs
│ ├── FormError.cs
│ ├── ICountriesService.cs
│ ├── ICurrencyService.cs
│ ├── IHasLanguage.cs
│ ├── IHasSettings.cs
│ ├── ISeoInfoService.cs
│ ├── ISpaRouteService.cs
│ ├── IWorkContextAccessor.cs
│ ├── Image.cs
│ ├── Interaction/
│ │ ├── Client.cs
│ │ ├── Page.cs
│ │ ├── UserEvent.cs
│ │ └── UserSession.cs
│ ├── Language.cs
│ ├── LinkList/
│ │ ├── CategoryMenuLink.cs
│ │ ├── MenuLink.cs
│ │ ├── MenuLinkList.cs
│ │ ├── ProductMenuLink.cs
│ │ └── Services/
│ │ └── ILinkListService.cs
│ ├── LocalizedString.cs
│ ├── LoginProvider.cs
│ ├── Security/
│ │ ├── AccountState.cs
│ │ ├── ChangePassword.cs
│ │ ├── ChangeTwoFactorAuthenticationModel.cs
│ │ ├── ChangeTwoFactorAuthenticationResult.cs
│ │ ├── ConfirmEmailModel.cs
│ │ ├── CustomSignInResult.cs
│ │ ├── Events/
│ │ │ ├── UserDeletedEvent.cs
│ │ │ ├── UserLoginEvent.cs
│ │ │ └── UserRegisteredEvent.cs
│ │ ├── ExternalUserLoginInfo.cs
│ │ ├── ForgotPassword.cs
│ │ ├── ForgotPasswordModel.cs
│ │ ├── Login.cs
│ │ ├── OrganizationRegistration.cs
│ │ ├── OrganizationUserRegistration.cs
│ │ ├── PasswordChangeResult.cs
│ │ ├── RemovePhoneNumberResult.cs
│ │ ├── ResetPassword.cs
│ │ ├── ResetPasswordByCodeModel.cs
│ │ ├── ResetPasswordModel.cs
│ │ ├── Role.cs
│ │ ├── SecurityConstants.cs
│ │ ├── SecurityErrorDescriber.cs
│ │ ├── Specifications/
│ │ │ ├── CanUserLoginToStoreSpecification.cs
│ │ │ ├── IsUserLockedByRequiredEmailVerificationSpecification.cs
│ │ │ ├── IsUserLockedOutSpecification.cs
│ │ │ ├── IsUserPasswordExpiredSpecification.cs
│ │ │ ├── IsUserSuspendedSpecification.cs
│ │ │ └── IsUserTemporaryLockedOutSpecification.cs
│ │ ├── UpdatePhoneNumberModel.cs
│ │ ├── UpdatePhoneNumberResult.cs
│ │ ├── User.cs
│ │ ├── UserActionIdentityResult.cs
│ │ ├── UserRegistration.cs
│ │ ├── UserRegistrationByInvitation.cs
│ │ ├── UserSearchResult.cs
│ │ ├── UserUpdateInfo.cs
│ │ ├── UsersInvitation.cs
│ │ ├── ValidateTokenModel.cs
│ │ ├── VerifyCodeViewModel.cs
│ │ └── VerifyPhoneNumberModel.cs
│ ├── SeoInfo.cs
│ ├── SeoLinksType.cs
│ ├── SettingEntry.cs
│ ├── SlugInfoRequest.cs
│ ├── SlugInfoResult.cs
│ ├── SlugRoutingData.cs
│ ├── SpaThemeContext.cs
│ ├── StaticContent/
│ │ ├── Blog.cs
│ │ ├── BlogArticle.cs
│ │ ├── BlogSearchCriteria.cs
│ │ ├── ContentInThemeSearchCriteria.cs
│ │ ├── ContentItem.cs
│ │ ├── ContentPage.cs
│ │ ├── IContentBlobProvider.cs
│ │ ├── IStaticContentItemFactory.cs
│ │ ├── IStaticContentLoader.cs
│ │ ├── IStaticContentLoaderFactory.cs
│ │ ├── IStaticContentService.cs
│ │ └── StaticContentSearchCriteria.cs
│ ├── StorefrontNotification.cs
│ ├── StorefrontNotificationType.cs
│ ├── Stores/
│ │ ├── IStoreService.cs
│ │ ├── Store.cs
│ │ └── StoreStatus.cs
│ ├── SwaggerCustomSchemaIdAttribute.cs
│ ├── Term.cs
│ ├── TermExtensions.cs
│ ├── VirtoCommerce.Storefront.Model.csproj
│ └── WorkContext.cs
├── VirtoCommerce.Storefront.Tests/
│ ├── Features/
│ │ ├── CustomServiceCollection.cs
│ │ ├── FeaturesAgentTests.cs
│ │ └── Samples/
│ │ ├── empty_data.json
│ │ ├── empty_file.json
│ │ ├── full_data.json
│ │ ├── full_data_with_conflicts.json
│ │ ├── full_data_with_disabled_feature.json
│ │ ├── full_data_without_replaces.json
│ │ └── test_data.json
│ ├── JsonConverterTests.cs
│ ├── LiquidThemeEngine/
│ │ └── ShopifyLiquidThemeEngineTests.cs
│ ├── Model/
│ │ └── MoneyOperationTests.cs
│ ├── ResponseCaching/
│ │ └── ResponseCachingTests.cs
│ ├── Routing/
│ │ ├── Infrastructure/
│ │ │ ├── DummyAntiforgery.cs
│ │ │ ├── RoutingDataResult.cs
│ │ │ └── RoutingTestingActionFilter.cs
│ │ └── PathStringExtensionsTests.cs
│ ├── Scriban/
│ │ ├── ScribanTests.cs
│ │ └── test.liquid
│ ├── ValueObjectTests.cs
│ └── VirtoCommerce.Storefront.Tests.csproj
├── VirtoCommerce.Storefront.sln
└── azuredeploy.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .deployment/storefront-app/argoDeploy.json
================================================
{
"artifactKey": "docker.pkg.github.com/virtocommerce/vc-storefront/storefront",
"deployRepo": "vc-deploy-dev",
"cmPath": "storefront-app/resources/kustomization.yaml",
"dev": {
"deployAppName": "storefront-dev",
"deployBranch": "dev",
"environmentId" : "dev",
"environmentName" : "Development",
"environmentType" : "staging",
"environmentUrl" : "https://st-storefront.dev.govirto.com/"
},
"qa": {
"deployAppName": "storefront-qa",
"deployBranch": "qa",
"environmentId" : "qa",
"environmentName" : "QA",
"environmentType" : "testing",
"environmentUrl" : "https://st-storefront.qa.govirto.com/"
},
"prod": {
"deployAppName": "storefront-demo",
"deployBranch": "demo",
"environmentId" : "demo",
"environmentName" : "Demo",
"environmentType" : "production",
"environmentUrl" : "https://st-storefront.demo.govirto.com/"
}
}
================================================
FILE: .editorconfig
================================================
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = crlf
trim_trailing_whitespace = true
insert_final_newline = true
# Project files
[*.csproj]
indent_size = 2
# JSON files
[*.json]
indent_size = 2
# Dotnet code style settings
[*.{cs,vb}]
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Use explicit accessibility modifiers
dotnet_style_require_accessibility_modifiers = true:suggestion
# Suggest more modern language features when available
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_prefer_inferred_tuple_names = true:suggestion
dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion
# CSharp code style settings
[*.cs]
# Prefer curly braces even for one line of code
csharp_prefer_braces = true:suggestion
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = true:suggestion
csharp_style_var_when_type_is_apparent = true:suggestion
csharp_style_var_elsewhere = true:suggestion
# Prefer method-like constructs to have a block body
csharp_style_expression_bodied_methods = false:none
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none
# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_preserve_single_line_statements = false
csharp_preserve_single_line_blocks = true
================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
*.bat text eol=crlf
*.cmd text eol=crlf
*.config text eol=crlf
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
Please provide detailed information about your issue, thank you!
Version info:
- Browser version:
- Platform version:
- Storefront version:
### Expected behavior
### Actual behavior
### Steps to reproduce
1.
2.
3.
================================================
FILE: .github/pull_request_template.md
================================================
Description:
--
QA-test:
Demo-test:
Download artifact URL:
================================================
FILE: .github/workflows/deploy.yml
================================================
# v1.3.0
name: VC deployment
on:
workflow_dispatch:
inputs:
artifactUrl:
description: 'Full link to artifact docker image or artifact download url'
required: true
deployEnvironment:
description: 'Deployment environment type. Allowed values: dev, qa, prod'
required: true
default: 'dev'
deployConfigPath:
description: 'Full path to argoDeploy.json'
required: true
default: 'argoDeploy.json'
jiraKeys:
description: 'Deployed artifact Jira keys (for cycle time report)'
required: false
default: ''
jobs:
cd:
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }}
CLOUD_INSTANCE_BASE_URL: ${{secrets.CLOUD_INSTANCE_BASE_URL}}
CLIENT_ID: ${{secrets.CLIENT_ID}}
CLIENT_SECRET: ${{secrets.CLIENT_SECRET}}
SLEEP_TIME: '5m'
ARGO_SERVER: 'argo.govirto.com'
steps:
- name: Install vc-build
run: |
dotnet tool install --global VirtoCommerce.GlobalTool
- name: Set Output
id: app-name
run: |
if [ ${{ github.ref }} == 'refs/heads/master' ]; then
echo "IS master branch"
echo "APP=vcptcore-qa" >> $GITHUB_OUTPUT
elif [ ${{ github.ref }} == 'refs/heads/dev' ]; then
echo "IS dev branch"
echo "APP=vcst-dev" >> $GITHUB_OUTPUT
fi
- name: Read deployment config
uses: VirtoCommerce/vc-github-actions/get-deploy-param@master
id: deployConfig
with:
envName: ${{ github.event.inputs.deployEnvironment }}
deployConfigPath: ${{ github.event.inputs.deployConfigPath }}
- name: Start deployment
uses: VirtoCommerce/vc-github-actions/gh-deployments@master
id: deployment
with:
step: start
token: ${{ secrets.GITHUB_TOKEN }}
env: ${{ steps.deployConfig.outputs.environmentName }}
no_override: false
- name: Update vcptcore-qa environment
if: ${{ github.ref == 'refs/heads/master' }}
run: |
vc-build CloudEnvSetParameter -EnvironmentName ${{ steps.app-name.outputs.APP }} -CloudToken ${{ secrets.VCPTCORE_PLATFORM_TOKEN }} -HelmParameters storefront.image.tag=${{ github.event.inputs.artifactUrl }}
- name: Update vcst-dev environment
if: ${{ github.ref == 'refs/heads/dev' }}
run: |
vc-build CloudEnvSetParameter -EnvironmentName ${{ steps.app-name.outputs.APP }} -CloudToken ${{ secrets.VCST_PLATFORM_TOKEN }} -HelmParameters storefront.image.tag=${{ github.event.inputs.artifactUrl }}
- name: DEPLOY_STATE::successful
if: success()
run: echo "DEPLOY_STATE=successful" >> $GITHUB_ENV
- name: DEPLOY_STATE::failed
if: failure()
run: echo "DEPLOY_STATE=failed" >> $GITHUB_ENV
- name: Update GitHub deployment status
uses: VirtoCommerce/vc-github-actions/gh-deployments@master
if: always()
with:
step: finish
token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status }}
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
- name: Push Deployment Info to Jira
if: ${{ env.CLOUD_INSTANCE_BASE_URL != 0 && env.CLIENT_ID != 0 && env.CLIENT_SECRET != 0 && github.event.inputs.jiraKeys != '' && always() }}
id: push_deployment_info_to_jira
uses: VirtoCommerce/jira-upload-deployment-info@master
env:
CLOUD_INSTANCE_BASE_URL: ${{secrets.CLOUD_INSTANCE_BASE_URL}}
CLIENT_ID: ${{secrets.CLIENT_ID}}
CLIENT_SECRET: ${{secrets.CLIENT_SECRET}}
with:
cloud-instance-base-url: ${{ secrets.CLOUD_INSTANCE_BASE_URL }}
client-id: ${{ secrets.CLIENT_ID }}
client-secret: ${{ secrets.CLIENT_SECRET }}
deployment-sequence-number: ${{ github.run_id }}
update-sequence-number: ${{ github.run_id }}
issue-keys: ${{ github.event.inputs.jiraKeys }}
display-name: ${{ steps.deployConfig.outputs.deployAppName }}
url: ${{ steps.deployConfig.outputs.environmentUrl }}
description: 'Deployment to the ${{ steps.deployConfig.outputs.environmentName }} environment'
last-updated: '${{github.event.head_commit.timestamp}}'
state: '${{ env.DEPLOY_STATE }}'
pipeline-id: '${{ github.repository }} ${{ github.workflow }}'
pipeline-display-name: 'Workflow: ${{ github.workflow }} (#${{ github.run_number }})'
pipeline-url: '${{github.event.repository.html_url}}/actions/runs/${{github.run_id}}'
environment-id: ${{ steps.deployConfig.outputs.environmentId }}
environment-display-name: ${{ steps.deployConfig.outputs.environmentName }}
environment-type: ${{ steps.deployConfig.outputs.environmentType }}
================================================
FILE: .github/workflows/main.yml
================================================
# v1.2.1
name: Storefront CI
on:
workflow_dispatch:
inputs:
forceLatest:
description: "Flag to set dev-linux-latest flag on workflow_dispatch event. Allowed values true or false."
required: false
default: "false"
push:
paths-ignore:
- '.github/**'
- 'docs/**'
- 'build/**'
- 'README.md'
- 'LICENSE'
- '**/argoDeploy.json'
branches: [ master, dev ]
jobs:
ci:
if: ${{ github.actor != 'dependabot[bot]' && (github.event.pull_request.head.repo.full_name == github.repository || github.event.pull_request.head.repo.full_name == '') }} # Check that PR not from forked repo and not from Dependabot
runs-on: ubuntu-latest
env:
CLOUD_INSTANCE_BASE_URL: ${{secrets.CLOUD_INSTANCE_BASE_URL}}
CLIENT_ID: ${{secrets.CLIENT_ID}}
CLIENT_SECRET: ${{secrets.CLIENT_SECRET}}
SONAR_TOKEN: ${{secrets.SONAR_TOKEN}}
GITHUB_TOKEN: ${{ secrets.REPO_TOKEN }}
NUGET_KEY: ${{ secrets.NUGET_KEY }}
BLOB_SAS: ${{ secrets.BLOB_TOKEN }}
IMAGE_NAME: 'storefront'
PACKAGE_SERVER: 'ghcr.io'
PUBLISH_TO_DOCKER: 'true'
UPDATE_LATEST_TAG: 'true'
VERSION_SUFFIX: ''
BUILD_STATE: 'failed'
RELEASE_STATUS: 'false'
BUILD_DOCKER: 'false'
outputs:
artifactUrl: ${{ steps.image.outputs.taggedVersion }}
jira-keys: ${{ steps.jira_keys.outputs.jira-keys }}
steps:
- name: Set up Node 20
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Set up Java 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Set variables
if: ${{ github.event_name == 'workflow_dispatch' }}
run: |
echo "PUBLISH_TO_DOCKER=false" >> $GITHUB_ENV
echo "UPDATE_LATEST_TAG=${{ github.event.inputs.forceLatest }}" >> $GITHUB_ENV
- name: Set RELEASE_STATUS
if: ${{ github.ref == 'refs/heads/master' && github.event_name == 'push' }}
run: |
echo "RELEASE_STATUS=true" >> $GITHUB_ENV
- name: Set BUILD_DOCKER
if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/dev-dockerenv' || github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/master') }}
run: |
echo "BUILD_DOCKER=true" >> $GITHUB_ENV
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install VirtoCommerce.GlobalTool
uses: VirtoCommerce/vc-github-actions/setup-vcbuild@master
- name: Install dotnet-sonarscanner
run: dotnet tool install --global dotnet-sonarscanner
- name: Get Image Version
uses: VirtoCommerce/vc-github-actions/get-image-version@master
id: image
- name: Get changelog
id: changelog
uses: VirtoCommerce/vc-github-actions/changelog-generator@master
- name: Set VERSION_SUFFIX variable
run: |
if [ '${{ github.event_name }}' = 'workflow_dispatch' ]; then
echo "VERSION_SUFFIX=${{ steps.image.outputs.fullSuffix }}" >> $GITHUB_ENV
else
echo "VERSION_SUFFIX=${{ steps.image.outputs.suffix }}" >> $GITHUB_ENV
fi;
- name: Add version suffix
if: ${{ github.ref != 'refs/heads/master' }}
uses: VirtoCommerce/vc-github-actions/add-version-suffix@master
with:
versionSuffix: ${{ env.VERSION_SUFFIX }}
- name: SonarCloud Begin
uses: VirtoCommerce/vc-github-actions/sonar-scanner-begin@master
- name: Build
run: vc-build Compile
- name: Unit Tests
run: vc-build Test -TestsFilter "Category=Unit|Category=CI" -skip
- name: BUILD_STATE::successful
if: success()
run: echo "BUILD_STATE=successful" >> $GITHUB_ENV
- name: SonarCloud End
uses: VirtoCommerce/vc-github-actions/sonar-scanner-end@master
- name: Quality Gate
uses: VirtoCommerce/vc-github-actions/sonar-quality-gate@master
with:
login: ${{secrets.SONAR_TOKEN}}
- name: Packaging
run: vc-build Compress -skip Clean+Restore+Compile+Test
- name: Set artifactUrl value
id: artifactUrl
run: |
echo "DOCKER_URL=${{ env.PACKAGE_SERVER }}/${{github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.taggedVersion }}" >> $GITHUB_OUTPUT
- name: Build Docker Image
if: ${{ env.BUILD_DOCKER == 'true' }}
id: dockerBuild
uses: VirtoCommerce/vc-github-actions/build-docker-image@master
with:
tag: ${{ steps.image.outputs.taggedVersion }}
imageName: ${{ env.IMAGE_NAME }}
dockerFiles: 'https://raw.githubusercontent.com/VirtoCommerce/vc-docker/feat/net8/linux/storefront/Dockerfile'
- name: Publish Github Release
if: ${{ github.ref == 'refs/heads/master' }}
with:
changelog: ${{ steps.changelog.outputs.changelog }}
uses: VirtoCommerce/vc-github-actions/publish-github-release@master
- name: Docker Login
if: ${{ env.BUILD_DOCKER == 'true' }}
uses: docker/login-action@v3
with:
registry: ${{ env.PACKAGE_SERVER }}
username: $GITHUB_ACTOR
password: ${{ secrets.GITHUB_TOKEN }}
- name: Publish Docker Image
if: ${{ env.BUILD_DOCKER == 'true' }}
uses: VirtoCommerce/vc-github-actions/publish-docker-image@master
with:
image: ${{ steps.dockerBuild.outputs.imageName }}
tag: ${{ steps.image.outputs.taggedVersion }}
docker_user: ${{ secrets.DOCKER_USERNAME }}
docker_token: ${{ secrets.DOCKER_TOKEN }}
docker_hub: ${{ env.PUBLISH_TO_DOCKER }}
update_latest: ${{ env.UPDATE_LATEST_TAG }}
- name: Publish to SaaS ACR
if: ${{ github.ref == 'refs/heads/master' }}
run: |
docker login virtopaasregistrymain.azurecr.io -u VirtoPaaSRegistryMain -p ${{secrets.ACR_PASSWORD}}
docker tag ghcr.io/virtocommerce/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.taggedVersion }} virtopaasregistrymain.azurecr.io/saas/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.taggedVersion }}
docker push virtopaasregistrymain.azurecr.io/saas/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.taggedVersion }}
- name: Publish to ACR
run: |
docker login virtopaasregistrymain.azurecr.io -u vcst-token -p ${{ secrets.VCST_ACR_DOCKER_PASSWORD }}
docker tag ghcr.io/virtocommerce/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.taggedVersion }} virtopaasregistrymain.azurecr.io/vcst/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.taggedVersion }}
docker push virtopaasregistrymain.azurecr.io/vcst/${{ env.IMAGE_NAME }}:${{ steps.image.outputs.taggedVersion }}
- name: Add link to PR
if: ${{ github.event_name == 'pull_request' }}
uses: VirtoCommerce/vc-github-actions/publish-artifact-link@master
with:
artifactUrl: ${{ steps.artifactUrl.outputs.DOCKER_URL }}
- name: Parse Jira Keys from All Commits
uses: VirtoCommerce/vc-github-actions/get-jira-keys@master
if: always()
id: jira_keys
with:
release: ${{ env.RELEASE_STATUS }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Push Build Info to Jira
if: ${{ env.CLOUD_INSTANCE_BASE_URL != 0 && env.CLIENT_ID != 0 && env.CLIENT_SECRET != 0 && steps.jira_keys.outputs.jira-keys != '' && always() }}
id: push_build_info_to_jira
uses: VirtoCommerce/jira-upload-build-info@master
with:
cloud-instance-base-url: '${{ secrets.CLOUD_INSTANCE_BASE_URL }}'
client-id: '${{ secrets.CLIENT_ID }}'
client-secret: '${{ secrets.CLIENT_SECRET }}'
pipeline-id: '${{ github.repository }} ${{ github.workflow }}'
build-number: ${{ github.run_number }}
build-display-name: 'Workflow: ${{ github.workflow }} (#${{ github.run_number }})'
build-state: '${{ env.BUILD_STATE }}'
build-url: '${{github.event.repository.url}}/actions/runs/${{github.run_id}}'
update-sequence-number: '${{ github.run_id }}'
last-updated: '${{github.event.head_commit.timestamp}}'
issue-keys: '${{ steps.jira_keys.outputs.jira-keys }}'
commit-id: '${{ github.sha }}'
repo-url: '${{ github.event.repository.url }}'
build-ref-url: '${{ github.event.repository.url }}/actions/runs/${{ github.run_id }}'
- name: Confirm Jira Build Output
if: success()
run: |
echo "Jira Upload Build Info response: ${{ steps.push_build_info_to_jira.outputs.response }}"
deploy:
if: ${{ (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/master') && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')}}
needs: ci
runs-on: ubuntu-latest
env:
CONFIG_PATH: '.deployment/storefront-app/argoDeploy.json'
steps:
- name: Set deployment environment
id: deployEnv
run: |
if [ '${{ github.ref }}' = 'refs/heads/master' ]; then
echo "NAME=prod" >> $GITHUB_OUTPUT
else
echo "NAME=dev" >> $GITHUB_OUTPUT
fi;
- name: Invoke Module deployment workflow
uses: benc-uk/workflow-dispatch@v1
with:
workflow: VC deployment
token: ${{ secrets.REPO_TOKEN }}
inputs: '{ "artifactUrl": "${{ needs.ci.outputs.artifactUrl }}", "deployEnvironment": "${{ steps.deployEnv.outputs.NAME }}", "deployConfigPath": "${{ env.CONFIG_PATH }}", "jiraKeys":"${{ needs.ci.outputs.jira-keys }}" }'
================================================
FILE: .github/workflows/msteams.yml
================================================
name: Send message to Teams (regression PR)
on:
pull_request:
branches:
[regression]
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Send a message to Microsoft Teams
uses: VirtoCommerce/vc-github-actions/msteams-send-message@master
with:
body: '{
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
"themeColor": "0076D7",
"summary": "On ${{github.repository}} repository",
"sections": [
{
"activityTitle": "Regression PR created/updated",
"activitySubtitle": "By [${{ github.event.pull_request.user.login }}](${{ github.event.pull_request.user.url }}) on **[${{github.repository}}](${{github.server_url}}/${{github.repository}})** repository",
"activityImage":"${{ github.event.pull_request.user.avatar_url }}",
"facts": [
{
"name": "Repository",
"value": "[${{github.repository}}](${{github.server_url}}/${{github.repository}})"
},
{
"name": "Pull request",
"value": "[${{ github.event.pull_request.number }}](${{ github.event.pull_request._links.html.href }})"
},
{
"name": "Pull request title",
"value": "${{ github.event.pull_request.title }}"
}
],
"markdown": true
}
],
"potentialAction": [ {
"@type": "OpenUri",
"name": "View Pull Request",
"targets": [{
"os": "default",
"uri": "${{ github.event.pull_request._links.html.href }}"
}]
}]
}' # the body of the message
webhook_uri: ${{ secrets.PLATFORM_TEAMS_URI }}
================================================
FILE: .github/workflows/pr-ci.yml
================================================
# v3.800.4
name: PR build
on:
workflow_dispatch:
pull_request:
branches: [ master, dev ]
paths-ignore:
- '.github/**'
- 'docs/**'
- 'build/**'
- 'README.md'
- 'LICENSE'
- '**/argoDeploy.json'
jobs:
test:
uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.4
secrets:
sonarToken: ${{ secrets.SONAR_TOKEN }}
build:
uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.4
with:
uploadDocker: 'true'
imageName: 'storefront'
dockerFiles: 'https://raw.githubusercontent.com/VirtoCommerce/vc-docker/feat/net8/linux/storefront/Dockerfile'
secrets:
envPAT: ${{ secrets.REPO_TOKEN }}
================================================
FILE: .github/workflows/pr-deploy.yml
================================================
# v3.800.4
name: PR deploy
on:
pull_request:
branches: [ master, dev ]
types: [ labeled ]
jobs:
get-deployment-data:
if: ${{ github.event.label.name == 'deploy-qa' }}
runs-on: ubuntu-latest
env:
ARTIFACT_NAME: ${{ github.event.repository.name }}
DOCKER_CACHE_KEY: ''
DOCKER_TAR: 'image.tar'
outputs:
dockerShortKey: ${{ steps.cache-key.outputs.dockerShortKey }}
dockerFullKey: ${{ steps.cache-key.outputs.dockerFullKey }}
packageShortKey: ${{ steps.cache-key.outputs.packageShortKey }}
packageFullKey: ${{ steps.cache-key.outputs.packageFullKey }}
dockerTar: ${{ env.DOCKER_TAR }}
jiraKey: ${{ steps.jiraKey.outputs.qaTaskNumber }}
steps:
- uses: actions/checkout@v4
- name: Get Artifact Version
uses: VirtoCommerce/vc-github-actions/get-image-version@master
id: artifactVer
- name: Get cache key
uses: VirtoCommerce/vc-github-actions/cache-get-key@master
id: cache-key
with:
runnerOs: ${{ runner.os }}
artifactName: ${{ env.ARTIFACT_NAME }}
- name: Gets Jira key from PR body
id: jiraKey
uses: VirtoCommerce/vc-github-actions/pr-body-get-link@master
with:
skipArtifactUrl: 'true'
githubToken: ${{ secrets.REPO_TOKEN }}
publish:
needs:
get-deployment-data
uses: VirtoCommerce/.github/.github/workflows/publish-docker.yml@v3.800.4
with:
fullKey: ${{ needs.get-deployment-data.outputs.dockerFullKey }}
shortKey: '${{ needs.get-deployment-data.outputs.dockerShortKey }}-'
dockerTar: ${{ needs.get-deployment-data.outputs.dockerTar }}
secrets:
envPAT: ${{ secrets.GITHUB_TOKEN }}
dockerUser: ${{ secrets.DOCKER_USERNAME }}
dockerToken: ${{ secrets.DOCKER_TOKEN }}
deploy:
needs:
[publish, get-deployment-data]
uses: VirtoCommerce/.github/.github/workflows/deploy.yml@v3.800.4
with:
argoServer: 'argo.govirto.com'
artifactUrl: ${{ needs.publish.outputs.imagePath }}
matrix: '{"include":[{"envName": "qa", "confPath": ".deployment/storefront-app/argoDeploy.json"}]}'
taskNumber: ${{ needs.get-deployment-data.outputs.jiraKey }}
forceCommit: false
secrets:
envPAT: ${{ secrets.REPO_TOKEN }}
argoLogin: ${{ secrets.ARGOCD_LOGIN }}
argoPassword: ${{ secrets.ARGOCD_PASSWORD }}
comment-publish:
if: ${{ always() && github.event.label.name == 'deploy-qa' }}
needs:
publish
env:
MESSAGE_BODY: ':x: Docker image publish filed.'
runs-on: ubuntu-latest
steps:
- name: Set MESSAGE_BODY
if: ${{ needs.publish.result == 'success' }}
run: |
echo "MESSAGE_BODY=:heavy_check_mark: Docker image ${{ needs.publish.outputs.imagePath }} published" >> $GITHUB_ENV
- name: Add link to PR
if: ${{ needs.publish.result == 'success' }}
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
uses: VirtoCommerce/vc-github-actions/publish-artifact-link@master
with:
artifactUrl: ${{ needs.publish.outputs.imagePath }}
- uses: actions/github-script@v7
if: ${{ !(contains('skipped, cancelled', needs.publish.result )) }}
with:
#github-token: ${{secrets.GITHUB_TOKEN}}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '${{ env.MESSAGE_BODY }}'
})
comment-deploy:
if: ${{ always() && github.event.label.name == 'deploy-qa' }}
needs:
[ deploy, publish ]
runs-on: ubuntu-latest
env:
MESSAGE_BODY: ':x: QA deployment failed.'
steps:
- name: Set MESSAGE_BODY
if: ${{ needs.deploy.result == 'success' }}
run: |
echo "MESSAGE_BODY=:heavy_check_mark: Docker image ${{ needs.publish.outputs.imagePath }} deployed to QA" >> $GITHUB_ENV
- uses: actions/github-script@v7
if: ${{ !(contains('skipped, cancelled', needs.deploy.result )) }}
with:
#github-token: ${{secrets.GITHUB_TOKEN}}
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '${{ env.MESSAGE_BODY }}'
})
================================================
FILE: .github/workflows/release-branch.yml
================================================
# v3.800.5
name: Release - branch
on:
workflow_dispatch:
inputs:
incrementVersion:
description: 'Increment automatically minor\patch version before release created. If "none" version will not be incremented.'
required: true
default: 'none'
type: choice
options:
- none
jobs:
test:
uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.2
secrets:
sonarToken: ${{ secrets.SONAR_TOKEN }}
build:
uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.2
with:
uploadPackage: 'true'
uploadDocker: 'true'
eventName: ${{ github.event_name }}
imageName: 'storefront'
dockerFiles: 'https://raw.githubusercontent.com/VirtoCommerce/vc-docker/feat/net8/linux/storefront/Dockerfile'
forceVersionSuffix: 'false'
secrets:
envPAT: ${{ secrets.REPO_TOKEN }}
get-metadata:
runs-on: ubuntu-latest
env:
DOCKER_CACHE_KEY: ''
DOCKER_TAR: 'image.tar'
outputs:
dockerShortKey: ${{ steps.cache-key.outputs.dockerShortKey }}
dockerFullKey: ${{ steps.cache-key.outputs.dockerFullKey }}
packageShortKey: ${{ steps.cache-key.outputs.packageShortKey }}
packageFullKey: ${{ steps.cache-key.outputs.packageFullKey }}
dockerTar: ${{ env.DOCKER_TAR }}
changelog: ${{ steps.changelog.outputs.changelog }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get Changelog
id: changelog
uses: VirtoCommerce/vc-github-actions/changelog-generator@master
- name: Get Artifact Version
uses: VirtoCommerce/vc-github-actions/get-image-version@master
id: artifactVer
- name: Get cache key
uses: VirtoCommerce/vc-github-actions/cache-get-key@master
id: cache-key
with:
runnerOs: ${{ runner.os }}
artifactName: ${{ github.event.repository.name }}
publish-docker:
needs:
[build, test, get-metadata]
uses: VirtoCommerce/.github/.github/workflows/publish-docker.yml@v3.800.2
with:
fullKey: ${{ needs.get-metadata.outputs.dockerFullKey }}
shortKey: '${{ needs.get-metadata.outputs.dockerShortKey }}-'
dockerTar: ${{ needs.get-metadata.outputs.dockerTar }}
publishToDocker: 'true'
secrets:
envPAT: ${{ secrets.GITHUB_TOKEN }}
dockerUser: ${{ secrets.DOCKER_USERNAME }}
dockerToken: ${{ secrets.DOCKER_TOKEN }}
publish-github-release:
needs:
[build, test, get-metadata]
uses: VirtoCommerce/.github/.github/workflows/publish-github.yml@v3.800.2
with:
fullKey: ${{ needs.get-metadata.outputs.packageFullKey }}
shortKey: '${{ needs.get-metadata.outputs.packageShortKey }}-'
changeLog: '${{ needs.get-metadata.outputs.changeLog }}'
forceNuget: false
secrets:
envPAT: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/release.yml
================================================
# v1.0.0
name: Release - Quick release
on:
workflow_dispatch:
jobs:
release:
uses: VirtoCommerce/.github/.github/workflows/release.yml@v3.800.2
secrets:
envPAT: ${{ secrets.REPO_TOKEN }}
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/
/VirtoCommerce.Storefront/wwwroot/cms-content
.vscode
VirtoCommerce.Storefront/wwwroot/js/designer.bundle.js*
================================================
FILE: .nuke
================================================
VirtoCommerce.Storefront.sln
================================================
FILE: CONTRIBUTING.md
================================================
Contributing
-----------
We welcome & recognize contributors to Virto Commerce. There are many benefits available for our contributers, from special licensing to project involvement and access to private repositories. Follow the guide below to contribute:
1. Before starting work on a new contribution, take a moment and search the commits and issues for similar proposals.
2. Fork the Virto Commerce repository into your account according to GitHub Fork a Repo document.
3. Make your changes. Use `dev` branch because it contains the latest stable code. We also recommend you test your code before contributing.
4. Once ready to commit your changes, create a pull request to `dev` branch according to GitHub Create a Pull Request.
5. Once received, the Virto Commerce development team will review your contribution and if approved, will pull your request to the appropriate branch.
================================================
FILE: Directory.Build.props
================================================
VirtoCommerce
Copyright © 2011-2024 Virto Commerce. All rights reserved
VirtoCommerce
8.3.0
$(VersionSuffix)-$(BuildNumber)
================================================
FILE: LICENSE
================================================
Copyright (c) Virto Solutions LTD. All rights reserved.
Licensed under the Virto Commerce Open Software License (the "License"); you
may not use this file except in compliance with the License. You may
obtain a copy of the License at
https://virtocommerce.com/open-source-license
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
================================================
FILE: README.md
================================================
# Virto Commerce Storefront Kit
[](https://github.com/VirtoCommerce/vc-storefront/actions?query=workflow%3A"Storefront+CI") [](https://sonarcloud.io/dashboard?id=VirtoCommerce_vc-storefront) [](https://sonarcloud.io/dashboard?id=VirtoCommerce_vc-storefront) [](https://sonarcloud.io/dashboard?id=VirtoCommerce_vc-storefront) [](https://sonarcloud.io/dashboard?id=VirtoCommerce_vc-storefront) [](https://sonarcloud.io/api/project_badges/measure?project=VirtoCommerce_vc-storefront&branch=dev&metric=ncloc)
[](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FVirtoCommerce%2Fvc-storefront%2Fmaster%2Fazuredeploy.json)
The Virto Commerce Storefront Kit is the official online shopping website based on the Virto Commerce Platform, written on ASP.NET 8. The website serves as a client application for the VC Platform and communicates solely through public APIs.
The Storefront Kit enables the creation of multiple distinct stores (websites) on top of the Virto Commerce Platform. Each store may have its own theme with specific layouts, yet still be based on the same catalog and customer data. This allows for versatile store configurations, such as:
* Different designs for various product categories.
* Regional-specific sites offering tailored product sets.
* Integration with multiple touchpoints for a true omnichannel experience.
## Key features
- Launch and host e-commerce themes on top of the Virto Commerce Platform.
- XAPI Gateway.
- Caching mechanism.
- Multi-store support.
- Multi-theme support.
- Server-side rendering.
And more.
## Architecture
For detailed information about the Virto Storefront Architecture, please refer to our [developer guide](https://docs.virtocommerce.org/storefront/developer-guide/)
## Technologies and frameworks used
- ASP.NET 8
- ASP.NET Identity Core
## Setup
For detailed setup information, please refer to [Quick Start](https://docs.virtocommerce.org/storefront/developer-guide/getting-started/quickstart-on-windows/) to deploy and run.
## Themes
### B2B Theme
View [B2B theme on GitHub](https://github.com/VirtoCommerce/vc-theme-b2b-vue).

### FAQ
#### Running the Storefront only on HTTP schema
- In order to run the platform only at HTTP schema in production mode, it's enough to pass only HTTP URLs in `--urls` argument of the `dotnet` command.
```console
dotnet VirtoCommerce.Storefront.dll --urls=http://localhost:5002
```
#### Running the Platform on HTTPS schema
- Install and trust HTTPS certificate
Run to trust the .NET Core SDK HTTPS development certificate:
```console
dotnet dev-certs https --trust
```
Read more about [enforcing HTTPS in ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-3.0&tabs=visual-studio#trust)
```console
dotnet VirtoCommerce.Storefront.dll --urls=https://localhost:4302/
```
- Trust the .Net Core Development Self-Signed Certificate. More details on trusting the self-signed certificate can be found [here](https://blogs.msdn.microsoft.com/robert_mcmurray/2013/11/15/how-to-trust-the-iis-express-self-signed-certificate/)
#### Forward the scheme for Linux and non-IIS reverse proxies
Apps that call UseHttpsRedirection and UseHsts put a site into an infinite loop if deployed to an Azure Linux App Service, Azure Linux virtual machine (VM), Linux container or behind any other reverse proxy besides IIS. TLS is terminated by the reverse proxy, and Kestrel isn't made aware of the correct request scheme. OAuth and OIDC also fail in this configuration because they generate incorrect redirects. UseIISIntegration adds and configures Forwarded Headers Middleware when running behind IIS, but there's no matching automatic configuration for Linux (Apache or Nginx integration).
To forward the scheme from the proxy in non-IIS scenarios, set `ASPNETCORE_FORWARDEDHEADERS_ENABLED` environment variable to `true`.
For more details on how it works, see the Microsoft [documentation](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-5.0#forward-the-scheme-for-linux-and-non-iis-reverse-proxies).
## References
- Virto Commerce Documentation: https://docs.virtocommerce.org
- Home: https://virtocommerce.com
- Community: https://www.virtocommerce.org
- [Download Latest Release](https://github.com/VirtoCommerce/vc-storefront/releases/latest)
## License
Copyright (c) Virto Solutions LTD. All rights reserved.
Licensed under the Virto Commerce Open Software License (the "License"); you
may not use this file except in compliance with the License. You may
obtain a copy of the License at
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied.
================================================
FILE: VirtoCommerce.LiquidThemeEngine/Converters/CountryConverter.cs
================================================
using System.Globalization;
using System.Linq;
using System.Web;
using Newtonsoft.Json;
using VirtoCommerce.Storefront.Model;
namespace VirtoCommerce.LiquidThemeEngine.Converters
{
public static class CountryConverter
{
public static string ToOptionTag(this Country country)
{
var regions = "[]";
if (country.Regions != null)
{
regions = JsonConvert.SerializeObject(country.Regions.Select(r => r.Name));
}
return string.Format(CultureInfo.InvariantCulture, "",
HttpUtility.HtmlAttributeEncode(country.Name),
HttpUtility.HtmlAttributeEncode(regions)
);
}
}
}
================================================
FILE: VirtoCommerce.LiquidThemeEngine/Exceptions/SaasCompileException.cs
================================================
using System;
using System.IO;
using System.Runtime.Serialization;
namespace DotLiquid.ViewEngine.Exceptions
{
[Serializable]
public class SaasCompileException : Exception
{
[Obsolete(DiagnosticId = "SYSLIB0051")]
protected SaasCompileException(SerializationInfo info, StreamingContext context) : base(info, context)
{
SassLine = info.GetString("SassLine");
}
public override string Message
{
get
{
return base.Message + "\n\r" + this.ToString();
}
}
public string SassLine
{
get;
private set;
}
public override string ToString()
{
return String.Format("Line: {0}\n\rCompiler error: {1}", SassLine, _innerException != null ? _innerException.ToString() : "");
}
private Exception _innerException;
public SaasCompileException(string filename, string sass, Exception innerException) : base("Failed to compile sass file \"" + filename + "\"")
{
_innerException = innerException;
if (innerException.Message.StartsWith("stdin"))
{
var lineNumber = Int32.Parse(innerException.Message.Split(':')[1]);
this.SassLine = ReadLine(sass, lineNumber);
}
}
private static string ReadLine(string text, int lineNumber)
{
var reader = new StringReader(text);
string line;
int currentLineNumber = 0;
do
{
currentLineNumber += 1;
line = reader.ReadLine();
}
while (line != null && currentLineNumber < lineNumber);
return (currentLineNumber == lineNumber) ? line : string.Empty;
}
[Obsolete(DiagnosticId = "SYSLIB0051")]
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue("SassLine", SassLine);
}
}
}
================================================
FILE: VirtoCommerce.LiquidThemeEngine/Extensions/GraphQLResponseExtensions.cs
================================================
using System;
using GraphQL;
using Newtonsoft.Json;
using VirtoCommerce.Storefront.Model.Common;
using VirtoCommerce.Storefront.Model.Common.Exceptions;
namespace VirtoCommerce.LiquidThemeEngine.Extensions
{
public static class GraphQLResponseExtensions
{
public static void ThrowIfHasErrors(this IGraphQLResponse response)
{
if (response == null)
{
throw new ArgumentNullException(nameof(response));
}
if (!response.Errors.IsNullOrEmpty())
{
throw new StorefrontException(JsonConvert.SerializeObject(response.Errors, Formatting.Indented));
}
}
}
}
================================================
FILE: VirtoCommerce.LiquidThemeEngine/Extensions/StringExtensions.cs
================================================
using System.Globalization;
namespace DotLiquid.ViewEngine.Extensions
{
public static class StringExtensions
{
public static string TrimQuotes(this string input)
{
return input.Trim('\'', '\"');
}
public static CultureInfo TryGetCultureInfo(this string languageCode)
{
try
{
return !string.IsNullOrEmpty(languageCode) ? CultureInfo.CreateSpecificCulture(languageCode) : null;
}
catch
{
return null;
}
}
public static int SafeParseInt(this string input, int defaultValue = default)
{
return int.TryParse(input, out var result) ? result : defaultValue;
}
}
}
================================================
FILE: VirtoCommerce.LiquidThemeEngine/Extensions/UriExtensions.cs
================================================
using System;
using System.Linq;
using System.Web;
using VirtoCommerce.Storefront.Model.Common;
namespace VirtoCommerce.LiquidThemeEngine.Extensions
{
public static class UriExtensions
{
///
/// Sets the given parameter value in the query string.
///
///
/// Name of the parameter to set.
/// Value for the parameter to set. Pass null to remove the parameter with given name.
/// Url with given parameter value.
public static Uri SetQueryParameter(this Uri url, string name, string value)
{
var qs = HttpUtility.ParseQueryString(url.Query);
if (value != null)
{
qs[name] = value;
}
else
{
qs.Remove(name);
}
var result = new UriBuilder(url)
{
Query = string.Join("&", qs.AllKeys.Select(key => string.Join("=", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(qs[key]))))
};
return result.Uri;
}
public static Uri SetQueryParameters(this Uri uri, IHasQueryKeyValues hasQueryKeyValues)
{
if (uri == null)
{
throw new ArgumentNullException(nameof(uri));
}
if (hasQueryKeyValues == null)
{
throw new ArgumentNullException(nameof(hasQueryKeyValues));
}
foreach (var keyValue in hasQueryKeyValues.GetQueryKeyValues())
{
uri = uri.SetQueryParameter(keyValue.Key, keyValue.Value);
}
return uri;
}
}
}
================================================
FILE: VirtoCommerce.LiquidThemeEngine/Filters/ArrayFilters.cs
================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using VirtoCommerce.Storefront.Model.Common;
namespace VirtoCommerce.LiquidThemeEngine.Filters
{
public static partial class ArrayFilters
{
public static object Tree(object input, string propName, string titlePropName, string delimiter, string sortByPropName)
{
var tree = new List();
var enumerable = input as IEnumerable;
if (enumerable != null)
{
var elementType = enumerable.GetType().GetEnumerableType();
var propInfo = elementType.GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var titlePropInfo = elementType.GetProperty(titlePropName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var sortByPropInfo = elementType.GetProperty(sortByPropName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (propInfo != null)
{
var charDelimiter = delimiter.ToCharArray();
foreach (var element in enumerable)
{
var path = propInfo.GetValue(element) as string;
var parts = path.Split(charDelimiter);
for (var i = parts.Length; i > 0; i--)
{
path = string.Join(delimiter, parts.Take(i));
var treeNode = tree.FirstOrDefault(n => n.Path == path);
if (treeNode == null)
{
tree.Add(new TreeNode
{
Level = i,
ParentPath = string.Join(delimiter, parts.Take(Math.Max(0, i - 1))),
Path = path,
Priority = sortByPropInfo != null ? sortByPropInfo.GetValue(element) as int? : null,
Title = titlePropInfo != null ? titlePropInfo.GetValue(element) as string : null
});
}
}
}
foreach (var treeNode in tree.OrderBy(n => n.Priority))
{
if (!string.IsNullOrEmpty(treeNode.ParentPath))
{
var parent = tree.FirstOrDefault(n => n.Path == treeNode.ParentPath);
if (parent != null)
{
treeNode.Parent = parent;
parent.Children.Add(treeNode);
}
}
treeNode.AllChildren = tree.Where(n => n.Path.StartsWith(treeNode.Path + delimiter)).ToList();
}
}
}
return tree;
}
///
/// Filter the elements of an array by a given condition
/// {% assign sorted = pages | where:"propName","==","value" %}
///
///
///
///
public static object Where(object input, string propName, string op, string value)
{
var retVal = input;
var enumerable = retVal as IEnumerable;
if (enumerable != null)
{
var queryable = enumerable.AsQueryable();
var elementType = enumerable.GetType().GetEnumerableType();
var paramX = Expression.Parameter(elementType, "x");
var propInfo = elementType.GetProperty(propName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var left = Expression.Property(paramX, propInfo);
var objValue = ParseString(value);
var right = Expression.Constant(objValue);
BinaryExpression binaryOp;
if (op.EqualsInvariant("=="))
binaryOp = Expression.Equal(left, right);
else if (op.EqualsInvariant("!="))
binaryOp = Expression.NotEqual(left, right);
else if (op.EqualsInvariant(">"))
binaryOp = Expression.GreaterThan(left, right);
else if (op.EqualsInvariant(">="))
binaryOp = Expression.GreaterThanOrEqual(left, right);
else if (op.EqualsInvariant("=<"))
binaryOp = Expression.LessThan(left, right);
else if (op.EqualsInvariant("contains"))
{
Expression expr = null;
if (propInfo.PropertyType == typeof(string))
{
var containsMethod = typeof(string).GetMethods().First(x => x.Name == "Contains");
expr = Expression.Call(left, containsMethod, right);
}
else
{
var containsMethod = typeof(Enumerable).GetMethods().First(x => x.Name == "Contains" && x.GetParameters().Count() == 2).MakeGenericMethod(new Type[] { objValue.GetType() });
expr = Expression.Call(containsMethod, left, right);
}
//where(x=> x.Tags.Contains(y))
binaryOp = Expression.Equal(expr, Expression.Constant(true));
}
else
binaryOp = Expression.LessThanOrEqual(left, right);
var delegateType = typeof(Func<,>).MakeGenericType(elementType, typeof(bool));
//Construct Func = (x) => x.propName == value expression
var lambda = Expression.Lambda(delegateType, binaryOp, paramX);
//Find Queryable.Where(Expression>) method
var whereMethod = typeof(Queryable).GetMethods()
.Where(x => x.Name == "Where")
.Select(x => new { M = x, P = x.GetParameters() })
.Where(x => x.P.Length == 2
&& x.P[0].ParameterType.IsGenericType
&& x.P[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& x.P[1].ParameterType.IsGenericType
&& x.P[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>))
.Select(x => new { x.M, A = x.P[1].ParameterType.GetGenericArguments() })
.Where(x => x.A[0].IsGenericType
&& x.A[0].GetGenericTypeDefinition() == typeof(Func<,>))
.Select(x => new { x.M, A = x.A[0].GetGenericArguments() })
.Where(x => x.A[0].IsGenericParameter
&& x.A[1] == typeof(bool))
.Select(x => x.M)
.SingleOrDefault();
retVal = whereMethod.MakeGenericMethod(elementType).Invoke(null, new object[] { queryable, lambda });
}
return retVal;
}
///
/// Sorts the elements of an array by a given attribute of an element in the array.
/// {% assign sorted = pages | sort:"date:desc;name" %}
///
///
///
///
public static object SortList(object input, string sort)
{
var retVal = input;
IEnumerable enumerable = retVal as IEnumerable;
IMutablePagedList muttablePagedList = input as IMutablePagedList;
var sortInfos = SortInfo.Parse(sort).ToList();
if (muttablePagedList != null)
{
muttablePagedList.Slice(muttablePagedList.PageNumber, muttablePagedList.PageSize, sortInfos);
}
if (enumerable != null)
{
//Queryable.Cast(input).OrderBySortInfos(sortInfos) call by reflection
var queryable = enumerable.AsQueryable();
var elementType = enumerable.GetType().GetEnumerableType();
MethodInfo castMethodInfo = typeof(Queryable).GetMethods().First(x => x.Name == "Cast" && x.IsGenericMethod);
castMethodInfo = castMethodInfo.MakeGenericMethod(new Type[] { elementType });
var genericQueryable = castMethodInfo.Invoke(null, new object[] { queryable });
var orderBySortInfosMethodInfo = typeof(IQueryableExtensions).GetMethod("OrderBySortInfos");
orderBySortInfosMethodInfo = orderBySortInfosMethodInfo.MakeGenericMethod(new Type[] { elementType });
retVal = orderBySortInfosMethodInfo.Invoke(null, new object[] { genericQueryable, sortInfos.ToArray() });
}
return retVal;
}
private static object ParseString(string str)
{
int intValue;
double doubleValue;
char charValue;
bool boolValue;
TimeSpan timespan;
DateTime dateTime;
// Place checks higher if if-else statement to give higher priority to type.
if (int.TryParse(str, out intValue))
return intValue;
else if (double.TryParse(str, out doubleValue))
return doubleValue;
else if (TimeSpan.TryParse(str, out timespan))
return timespan;
else if (DateTime.TryParse(str, out dateTime))
return dateTime;
else if (char.TryParse(str, out charValue))
return charValue;
else if (bool.TryParse(str, out boolValue))
return boolValue;
return str;
}
}
}
================================================
FILE: VirtoCommerce.LiquidThemeEngine/Filters/CommerceFilters.cs
================================================
using System;
using System.Globalization;
using System.Linq;
using System.Threading;
namespace VirtoCommerce.LiquidThemeEngine.Filters
{
public static partial class CommerceFilters
{
#region Static Fields
private static readonly Lazy _cultures = new Lazy(
CreateCultures,
LazyThreadSafetyMode.ExecutionAndPublication);
#endregion
#region Public Properties
public static CultureInfo[] Cultures
{
get
{
return _cultures.Value;
}
}
#endregion
#region Public Methods and Operators
///
/// Return the three letter ISO currency code for the current thread.
///
/// current currency code in String
public static string CurrentCurrencyCode()
{
return new RegionInfo(Thread.CurrentThread.CurrentCulture.Name).ISOCurrencySymbol;
}
public static string CurrentCurrencyCode(string cultureName)
{
return new RegionInfo(cultureName).ISOCurrencySymbol;
}
///
/// Return the object which represents the place and language which matches the currency code which
/// the database is able to support. Fall back to Current Thread's culture if the currencyCode we requested doesn't
/// match.
///
/// the currency code to be matched for the culture
/// >
/// CultureInfo object
public static CultureInfo EffectiveCulture(string currencyCode)
{
var retVal = CultureInfo.CurrentCulture;
if (!CurrentCurrencyCode().Equals(currencyCode, StringComparison.OrdinalIgnoreCase))
{
// Find currency culture
var info =
Cultures.FirstOrDefault(
i =>
new RegionInfo(i.Name).ISOCurrencySymbol.Equals(
currencyCode,
StringComparison.OrdinalIgnoreCase));
retVal = info ?? retVal;
}
//.NET for swiss currency returns Fr where normally it should be CHF
if (retVal.Name.Equals("de-CH", StringComparison.InvariantCultureIgnoreCase))
{
retVal.NumberFormat.CurrencySymbol = "CHF";
}
return retVal;
}
///
/// Attempt to format the currency based on the browser's locale, but if that currency
/// is not in the database, then fallback to current thread's culture.
///
/// the amount to be formated
///
/// currency code which will be used to find the
/// effective culture
///
/// Formatted currency in String
public static string FormatCurrency(decimal amount, string currencyCode)
{
return string.Format(EffectiveCulture(currencyCode), "{0:c}", amount);
}
#endregion
#region Methods
private static CultureInfo[] CreateCultures()
{
return CultureInfo.GetCultures(CultureTypes.SpecificCultures);
}
#endregion
}
}
================================================
FILE: VirtoCommerce.LiquidThemeEngine/Filters/CommonFilters.cs
================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text.Encodings.Web;
using DotLiquid.ViewEngine.Extensions;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using PagedList.Core;
using Scriban;
using Scriban.Runtime;
using Scriban.Syntax;
using VirtoCommerce.LiquidThemeEngine.Extensions;
using VirtoCommerce.LiquidThemeEngine.JsonConverters;
using VirtoCommerce.LiquidThemeEngine.Objects;
using VirtoCommerce.Storefront.Model.Common;
namespace VirtoCommerce.LiquidThemeEngine.Filters
{
public static partial class CommonFilters
{
private static readonly string[] _poweredLinks = {
".NET ecommerce platform by Virto",
"Shopping Cart by Virto",
".NET Shopping Cart by Virto",
"ASP.NET Shopping Cart by Virto",
".NET ecommerce by Virto",
".NET ecommerce framework by Virto",
"ASP.NET ecommerce by Virto Commerce",
"ASP.NET ecommerce platform by Virto",
"ASP.NET ecommerce framework by Virto",
"Enterprise ecommerce by Virto",
"Enterprise ecommerce platform by Virto",
};
private static readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy(),
},
Converters = new List
{
new MutablePagedListAsArrayJsonConverter(),
},
};
public static object Default(object input, object value)
{
return input ?? value;
}
public static string Json(object input)
{
var serializedString = input != null ? JsonConvert.SerializeObject(input, _jsonSerializerSettings) : null;
return serializedString;
}
public static object ParseJson(string input)
{
var result = input != null ? JsonConvert.DeserializeObject(input, _jsonSerializerSettings) : null;
return result;
}
public static string PoweredBy(string signature)
{
var hashCode = (uint)signature.GetHashCode();
return _poweredLinks[hashCode % _poweredLinks.Length];
}
public static string Render(TemplateContext context, string input)
{
if (input == null)
{
return null;
}
var themeEngine = (ShopifyLiquidThemeEngine)context.TemplateLoader;
var result = themeEngine.RenderTemplateAsync(input, null, context.CurrentGlobal).GetAwaiter().GetResult();
return result;
}
public static string Antiforgery(TemplateContext context)
{
var themeEngine = (ShopifyLiquidThemeEngine)context.TemplateLoader;
var httpContext = themeEngine.HttpContext;
var antiforgery = httpContext.RequestServices.GetService();
var htmlContent = antiforgery.GetHtml(httpContext);
var writer = new StringWriter();
htmlContent.WriteTo(writer, HtmlEncoder.Default);
return writer.ToString();
}
public static string Layout(TemplateContext context, string layout)
{
if (!string.IsNullOrEmpty(layout))
{
var layoutSetter = (Action)context.GetValue(new ScriptVariableGlobal("layout_setter"));
layoutSetter(layout);
}
return null;
}
public static Paginate Paginate(TemplateContext context, object source, int pageSize = 20, string filterJson = null)
{
var pagedList = source as IPagedList;
var requestUrl = context.GetValue(new ScriptVariableGlobal("request_url")) as Uri;
var pageNumber = context.GetValue(new ScriptVariableGlobal("page_number"))?.ToString().SafeParseInt(1) ?? 1;
var effectivePageSize = context.GetValue(new ScriptVariableGlobal("page_size"))?.ToString().SafeParseInt(pageSize) ?? pageSize;
var @params = new NameValueCollection();
if (!string.IsNullOrEmpty(filterJson))
{
var values = JsonConvert.DeserializeObject>(filterJson);
foreach (var pair in values)
{
@params.Add(pair.Key, pair.Value);
}
}
switch (source)
{
case IMutablePagedList mutablePagedList:
mutablePagedList.Slice(pageNumber, effectivePageSize, mutablePagedList.SortInfos, @params);
pagedList = mutablePagedList;
break;
case ScriptObject scriptObject when scriptObject.Keys.Contains("total_count"):
pagedList = new StaticPagedList