├── .gitattributes ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Online.Store.Azure.Services ├── MediaService.cs ├── Online.Store.Azure.Services.csproj ├── SearchService.cs ├── ServiceBusService.cs └── StoreService.cs ├── Online.Store.AzureSearch ├── AzureSearchInitializer.cs ├── ISearchRepository.cs ├── Online.Store.AzureSearch.csproj ├── SearchRepository.cs └── SearchStoreRepository.cs ├── Online.Store.Core ├── Config.cs ├── DTOs │ ├── CartDTO.cs │ ├── CartItemsDTO.cs │ ├── Order.cs │ ├── PagedTopics.cs │ ├── Post.cs │ ├── Product.cs │ ├── ProductComponent.cs │ ├── ProductInfo.cs │ ├── ProductMedia.cs │ └── Topic.cs └── Online.Store.Core.csproj ├── Online.Store.DocumentDB ├── DocumentDBInitializer.cs ├── DocumentDBRepositoryBase.cs ├── DocumentDBStoreRepository.cs ├── IDocumentDBRepository.cs └── Online.Store.DocumentDB.csproj ├── Online.Store.RedisCache ├── IRedisCacheRepository.cs ├── Online.Store.RedisCache.csproj └── RedisCacheReposistory.cs ├── Online.Store.ServiceBus ├── IServiceBusRepository.cs ├── Online.Store.ServiceBus.csproj └── ServiceBusRepository.cs ├── Online.Store.SqlServer ├── ApplicationDbContext.cs ├── Migrations │ ├── 20171125180753_InitialCreate.Designer.cs │ ├── 20171125180753_InitialCreate.cs │ ├── 20171129181600_add_order_grand_total.Designer.cs │ ├── 20171129181600_add_order_grand_total.cs │ └── ApplicationDbContextModelSnapshot.cs └── Online.Store.SqlServer.csproj ├── Online.Store.Storage ├── IStorageRepository.cs ├── Online.Store.Storage.csproj ├── StorageInitializer.cs └── StorageRepository.cs ├── Online.Store.WebJob ├── Online.Store.WebJob.csproj ├── OrderService.cs ├── Program.cs ├── Startup.cs ├── appsettings.json.template └── run.cmd ├── Online.Store.sln ├── Online.Store ├── .gitignore ├── App_Data │ ├── db │ │ ├── identity-migrations.sql │ │ └── migrations.sql │ ├── devops │ │ ├── add-child-slot.ps1 │ │ ├── check-region-availability.ps1 │ │ ├── create-deployment-environment.ps1 │ │ ├── deploy-webjob.ps1 │ │ ├── devops-template.ps1 │ │ ├── init-child-resources.ps1 │ │ ├── init-geo-replication.ps1 │ │ ├── init-parent-resources.ps1 │ │ ├── init-primary-resources.ps1 │ │ ├── mac-linux │ │ │ ├── add-child-slot.ps1 │ │ │ ├── create-deployment-environment.ps1 │ │ │ ├── deploy-webjob.ps1 │ │ │ ├── init-child-resources.ps1 │ │ │ ├── init-geo-replication.ps1 │ │ │ ├── init-parent-resources.ps1 │ │ │ ├── init-primary-resources.ps1 │ │ │ ├── set-geo-replication.ps1 │ │ │ └── start-deployment.ps1 │ │ ├── set-geo-replication.ps1 │ │ ├── set-traffic-manager-endpoint.ps1 │ │ └── start-deployment.ps1 │ └── templates │ │ └── secrets.template.json ├── ClientApp │ ├── app │ │ ├── app.component.css │ │ ├── app.component.html │ │ ├── app.component.ts │ │ ├── app.constants.ts │ │ ├── app.module.browser.ts │ │ ├── app.module.server.ts │ │ ├── app.module.shared.ts │ │ ├── app.routes.ts │ │ ├── checkout │ │ │ ├── checkout.component.html │ │ │ ├── checkout.component.ts │ │ │ ├── checkout.css │ │ │ ├── checkout.html │ │ │ └── checkout.ts │ │ ├── core │ │ │ ├── core.module.ts │ │ │ └── services │ │ │ │ ├── account.service.ts │ │ │ │ ├── forum.service.ts │ │ │ │ ├── notifications.service.ts │ │ │ │ └── product.service.ts │ │ ├── forum │ │ │ ├── details │ │ │ │ ├── details.css │ │ │ │ ├── details.html │ │ │ │ ├── details.ts │ │ │ │ ├── topic-details.component.html │ │ │ │ └── topic-details.component.ts │ │ │ ├── forum.component.ts │ │ │ ├── forum.module.ts │ │ │ ├── forum.routes.ts │ │ │ ├── list │ │ │ │ ├── list.css │ │ │ │ ├── list.html │ │ │ │ ├── list.ts │ │ │ │ ├── topic-list.component.html │ │ │ │ └── topic-list.component.ts │ │ │ └── store │ │ │ │ ├── forum.actions.ts │ │ │ │ ├── forum.effects.ts │ │ │ │ ├── forum.reducer.ts │ │ │ │ └── forum.state.ts │ │ ├── home │ │ │ ├── home.component.html │ │ │ ├── home.component.ts │ │ │ └── home.css │ │ ├── models │ │ │ ├── cart-item.ts │ │ │ ├── cart.ts │ │ │ ├── login-vm.ts │ │ │ ├── message.ts │ │ │ ├── order.ts │ │ │ ├── paged-topics.ts │ │ │ ├── post.ts │ │ │ ├── product-component.ts │ │ │ ├── product-media.ts │ │ │ ├── product.ts │ │ │ ├── register-vm.ts │ │ │ ├── reply.ts │ │ │ ├── result-vm.ts │ │ │ ├── search-item.ts │ │ │ ├── topic.ts │ │ │ ├── user-cart.ts │ │ │ └── user.ts │ │ ├── navmenu │ │ │ ├── navbar.component.css │ │ │ ├── navbar.component.html │ │ │ ├── navbar.component.ts │ │ │ ├── navmenu.component.css │ │ │ ├── navmenu.component.html │ │ │ └── navmenu.component.ts │ │ ├── notifications │ │ │ ├── notifications.module.ts │ │ │ └── store │ │ │ │ ├── notifications.actions.ts │ │ │ │ ├── notifications.reducer.ts │ │ │ │ └── notifications.state.ts │ │ ├── orders │ │ │ ├── orders.component.html │ │ │ ├── orders.component.ts │ │ │ ├── orders.css │ │ │ ├── orders.html │ │ │ └── orders.ts │ │ ├── product │ │ │ ├── details │ │ │ │ ├── details.css │ │ │ │ ├── details.html │ │ │ │ ├── details.ts │ │ │ │ ├── product-details.html │ │ │ │ └── product-details.ts │ │ │ ├── list │ │ │ │ ├── list.css │ │ │ │ ├── list.html │ │ │ │ ├── list.ts │ │ │ │ ├── product-list.component.html │ │ │ │ └── product-list.component.ts │ │ │ ├── product.component.ts │ │ │ ├── product.module.ts │ │ │ ├── product.routes.ts │ │ │ └── store │ │ │ │ ├── product.action.ts │ │ │ │ ├── product.effects.ts │ │ │ │ ├── product.reducer.ts │ │ │ │ └── product.state.ts │ │ ├── reducers │ │ │ └── index.ts │ │ └── user │ │ │ ├── register │ │ │ ├── register.component.html │ │ │ ├── register.component.ts │ │ │ ├── register.css │ │ │ ├── register.html │ │ │ └── register.ts │ │ │ ├── store │ │ │ ├── user.actions.ts │ │ │ ├── user.effects.ts │ │ │ ├── user.reducer.ts │ │ │ └── user.state.ts │ │ │ ├── user.component.html │ │ │ ├── user.component.ts │ │ │ └── user.module.ts │ ├── boot.browser.ts │ ├── boot.server.ts │ └── test │ │ ├── boot-tests.ts │ │ └── karma.conf.js ├── Controllers │ ├── AccountController.cs │ ├── CartsController.cs │ ├── ForumController.cs │ ├── HomeController.cs │ ├── IdentityController.cs │ ├── ManageController.cs │ ├── OrdersController.cs │ ├── ProductsController.cs │ └── SearchController.cs ├── Core │ ├── AdUser.cs │ ├── B2CGraphClient.cs │ └── ModelStateExtensions.cs ├── Data │ └── ApplicationDbContext.cs ├── Extensions │ ├── AntiforgeryTokenMiddlewareExtensions.cs │ ├── AzureAdAuthenticationBuilderExtensions.cs │ ├── AzureAdOptions.cs │ ├── EmailSenderExtensions.cs │ └── UrlHelperExtensions.cs ├── Identity │ └── ApplicationIdentityDbContext.cs ├── Mappings │ ├── AutoMapperConfiguration.cs │ └── DomainToViewModelProfile.cs ├── Migrations │ ├── 20180123161723_InitialCreate.Designer.cs │ ├── 20180123161723_InitialCreate.cs │ └── ApplicationIdentityDbContextModelSnapshot.cs ├── Models │ ├── AccountViewModels │ │ ├── ExternalLoginViewModel.cs │ │ ├── ForgotPasswordViewModel.cs │ │ ├── LoginViewModel.cs │ │ ├── LoginWith2faViewModel.cs │ │ ├── LoginWithRecoveryCodeViewModel.cs │ │ ├── RegisterViewModel.cs │ │ └── ResetPasswordViewModel.cs │ ├── ApplicationUser.cs │ ├── ErrorViewModel.cs │ └── ManageViewModels │ │ ├── ChangePasswordViewModel.cs │ │ ├── EnableAuthenticatorViewModel.cs │ │ ├── ExternalLoginsViewModel.cs │ │ ├── GenerateRecoveryCodesViewModel.cs │ │ ├── IndexViewModel.cs │ │ ├── RemoveLoginViewModel.cs │ │ ├── SetPasswordViewModel.cs │ │ └── TwoFactorAuthenticationViewModel.cs ├── Online.Store.csproj ├── Program.cs ├── ScaffoldingReadMe.txt ├── Services │ ├── EmailSender.cs │ └── IEmailSender.cs ├── Startup.cs ├── ViewModels │ ├── OrderViewModel.cs │ ├── ProductComponentViewModel.cs │ ├── ProductMediaViewModel.cs │ ├── ProductViewModel.cs │ ├── ReplyViewModel.cs │ ├── ResultViewModel.cs │ ├── SearchResultViewModel.cs │ ├── UserCartViewModel.cs │ └── UserViewModel.cs ├── Views │ ├── Home │ │ └── Index.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ └── _Layout.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── appsettings.json ├── npm-shrinkwrap.json ├── package.json ├── tsconfig.json ├── webpack.config.js ├── webpack.config.vendor.js └── wwwroot │ ├── favicon.ico │ ├── images │ ├── 26507 │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ ├── 6.jpg │ │ ├── 7.jpg │ │ └── 8.jpg │ ├── 9779B001 │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ └── 6.jpg │ ├── DC-ZS70K │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.jpg │ │ ├── 5.jpg │ │ ├── 6.jpg │ │ └── 7.jpg │ ├── DSC-HX80 │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ └── 4.jpg │ ├── DSCW830 │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ └── 4.jpg │ ├── POLSP01W │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ └── 4.jpg │ └── app │ │ ├── schema-active-georeplication.png │ │ ├── schema-architecture.png │ │ ├── schema-chaining-georeplication.png │ │ ├── schema-child-resource-group.png │ │ ├── schema-devops-new-child-resource-groups.png │ │ ├── schema-distributing-data.png │ │ ├── schema-parent-resource-group.png │ │ ├── schema-primary-resource-group.png │ │ ├── schema-replicas.png │ │ ├── schema-traffic-manager-current-status.png │ │ └── schema-traffic-manager-new-status.png │ ├── products.json │ └── topics.json ├── README.md ├── appveyor.yml ├── global.json └── licence /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/Online.Store/bin/Debug/netcoreapp2.0/Online.Store.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/Online.Store", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "launchBrowser": { 19 | "enabled": true, 20 | "args": "${auto-detect-url}", 21 | "windows": { 22 | "command": "cmd.exe", 23 | "args": "/C start ${auto-detect-url}" 24 | }, 25 | "osx": { 26 | "command": "open" 27 | }, 28 | "linux": { 29 | "command": "xdg-open" 30 | } 31 | }, 32 | "env": { 33 | "ASPNETCORE_ENVIRONMENT": "Development" 34 | }, 35 | "sourceFileMap": { 36 | "/Views": "${workspaceFolder}/Views" 37 | } 38 | }, 39 | { 40 | "name": ".NET Core Attach", 41 | "type": "coreclr", 42 | "request": "attach", 43 | "processId": "${command:pickProcess}" 44 | } 45 | ,] 46 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/Online.Store/Online.Store.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /Online.Store.Azure.Services/MediaService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Online.Store.Storage; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Online.Store.Azure.Services 10 | { 11 | public class MediaService : IMediaService 12 | { 13 | private IStorageRepository _storageRepository; 14 | 15 | public MediaService(IConfiguration configuration, IStorageRepository storageRepository) 16 | { 17 | _storageRepository = storageRepository; 18 | 19 | _storageRepository.Connect(configuration["Storage:AccountName"], 20 | configuration["Storage:AccountKey"]); 21 | } 22 | 23 | public async Task UploadMediaAsync(Stream stream, string filename, string contentType) 24 | { 25 | string container = contentType.ToLower().Contains("image") ? "forum-images" : "forum-videos"; 26 | 27 | return await _storageRepository.UploadToContainerAsync(container, stream, filename, contentType); 28 | } 29 | } 30 | 31 | public interface IMediaService 32 | { 33 | Task UploadMediaAsync(Stream stream, string filename, string contentType); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Online.Store.Azure.Services/Online.Store.Azure.Services.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Online.Store.Azure.Services/SearchService.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Text; 6 | using Newtonsoft.Json.Serialization; 7 | using Newtonsoft.Json.Converters; 8 | using Online.Store.Core.DTOs; 9 | using Online.Store.AzureSearch; 10 | using System.Threading.Tasks; 11 | 12 | namespace Online.Store.Azure.Services 13 | { 14 | public class AzureSearchService : IAzureSearchService 15 | { 16 | private const string productIndexName = "product-index"; 17 | ISearchRepository _searchRepository; 18 | 19 | public AzureSearchService(ISearchRepository searchRepository) 20 | { 21 | _searchRepository = searchRepository; 22 | } 23 | 24 | public async Task> SearchProductsAsync(string term) 25 | { 26 | return await _searchRepository.SearchAsync(productIndexName, term, 27 | new List { "id", "title", "description", "image" }); 28 | } 29 | } 30 | 31 | public interface IAzureSearchService 32 | { 33 | Task> SearchProductsAsync(string term); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Online.Store.Azure.Services/ServiceBusService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Newtonsoft.Json; 3 | using Online.Store.Core.DTOs; 4 | using Online.Store.ServiceBus; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Online.Store.Azure.Services 11 | { 12 | public class ServiceBusService : IServiceBusService 13 | { 14 | IServiceBusRepository _serviceBusRepository; 15 | IConfiguration _configuration; 16 | 17 | public ServiceBusService(IServiceBusRepository serviceBusRepository, IConfiguration configuration) 18 | { 19 | _serviceBusRepository = serviceBusRepository; 20 | _configuration = configuration; 21 | } 22 | 23 | public async Task SubmitOrderAsync(Order order) 24 | { 25 | _serviceBusRepository.InitQueueClient(_configuration["ServiceBus:WriteAccessKeyName"], 26 | _configuration["ServiceBus:WriteAccessKey"], _configuration["ServiceBus:Queue"]); 27 | 28 | await _serviceBusRepository.SendAsync(order); 29 | } 30 | } 31 | 32 | public interface IServiceBusService 33 | { 34 | Task SubmitOrderAsync(Order order); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /Online.Store.AzureSearch/AzureSearchInitializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Online.Store.Core.DTOs; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Online.Store.AzureSearch 9 | { 10 | public class AzureSearchInitializer 11 | { 12 | static string _productsDataSource = "products-datasource"; 13 | static string _dataSourceCollection = "Items"; 14 | static string _productIndex = "product-index"; 15 | static string _productSuggester = "suggester"; 16 | static string _productsIndexer = "indexer"; 17 | public static async Task Initialize(IConfiguration configuration) 18 | { 19 | string _azureSearchServiceName = configuration["SearchService:Name"]; 20 | string _azureSearchServiceKey = configuration["SearchService:ApiKey"]; 21 | _productsIndexer += "-" + _azureSearchServiceName; 22 | 23 | // Create or Update the product index 24 | if (!string.IsNullOrEmpty(_azureSearchServiceName) && !string.IsNullOrEmpty(_azureSearchServiceKey)) 25 | { 26 | try 27 | { 28 | SearchStoreRepository _searchStoreRepository = new SearchStoreRepository(configuration); 29 | 30 | await _searchStoreRepository.CreateOrUpdateDocumentDbDataSourceAsync(_productsDataSource, _dataSourceCollection); 31 | 32 | await _searchStoreRepository.CreateOrUpdateIndexAsync(_productIndex, _productSuggester, new List { "title", "description" }); 33 | 34 | await _searchStoreRepository.CreateOrUpdateIndexerAsync(_productsIndexer, _productsDataSource, _productIndex); 35 | } 36 | catch(Exception ex) 37 | { 38 | 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Online.Store.AzureSearch/ISearchRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Online.Store.AzureSearch 7 | { 8 | public interface ISearchRepository 9 | { 10 | Task CreateOrUpdateDocumentDbDataSourceAsync(string dataSourceName, string dataSourceColletion); 11 | Task CreateOrUpdateIndexAsync(string indexName, string suggesterName, List suggesterFields); 12 | Task> SearchAsync(string searchIndex, string term, List returnFields) where T : class; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Online.Store.AzureSearch/Online.Store.AzureSearch.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Online.Store.AzureSearch/SearchStoreRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Search; 2 | using Microsoft.Extensions.Configuration; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace Online.Store.AzureSearch 8 | { 9 | public class SearchStoreRepository : SearchRepository 10 | { 11 | public SearchStoreRepository(IConfiguration configuration) 12 | { 13 | _documentDbEndpoint = string.Format("https://{0}.documents.azure.com:443/", configuration["DocumentDB:Endpoint"]); 14 | _documentDbAccountKey = configuration["DocumentDB:Key"]; 15 | _documentDbDatabase = configuration["DocumentDB:DatabaseId"]; 16 | 17 | string _azureSearchServiceName = configuration["SearchService:Name"]; 18 | string _azureSearchServiceKey = configuration["SearchService:ApiKey"]; 19 | 20 | _serviceClient = new SearchServiceClient(_azureSearchServiceName, new SearchCredentials(_azureSearchServiceKey)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Online.Store.Core/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Online.Store.Core 6 | { 7 | // Configuration 8 | public class Config 9 | { 10 | public static string WebRootPath = string.Empty; 11 | 12 | public static string ContentRootPath = string.Empty; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Online.Store.Core/DTOs/CartDTO.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Online.Store.Core.DTOs 6 | { 7 | public class CartDTO 8 | { 9 | [JsonProperty(PropertyName = "id")] 10 | public string Id { get; set; } 11 | 12 | [JsonProperty(PropertyName = "items")] 13 | public List Items { get; set; } 14 | 15 | [JsonProperty(PropertyName = "createddate")] 16 | public DateTime CreatedDate { get; set; } 17 | 18 | [JsonProperty(PropertyName = "updateddate")] 19 | public DateTime? UpdateDate { get; set; } 20 | 21 | public CartDTO() 22 | { 23 | this.Items = new List(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Online.Store.Core/DTOs/CartItemsDTO.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Online.Store.Core.DTOs 9 | { 10 | public class CartItemsDTO 11 | { 12 | [JsonProperty(PropertyName = "id")] 13 | public string Id { get; set; } 14 | 15 | [JsonProperty(PropertyName = "quantity")] 16 | public int Quantity { get; set; } 17 | 18 | [JsonProperty(PropertyName = "title")] 19 | public string Title { get; set; } 20 | 21 | [JsonProperty(PropertyName = "model")] 22 | public string Model { get; set; } 23 | 24 | [JsonProperty(PropertyName = "price")] 25 | public double Price { get; set; } 26 | 27 | [JsonProperty(PropertyName = "image")] 28 | public string Image { get; set; } 29 | 30 | [JsonProperty(PropertyName = "description")] 31 | public string Description { get; set; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Online.Store.Core/DTOs/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Online.Store.Core.DTOs 6 | { 7 | public class Order 8 | { 9 | public Order() 10 | { 11 | OrderDetails = new List(); 12 | } 13 | public int Id { get; set; } 14 | public string UserId { get; set; } 15 | public DateTime DateCreated { get; set; } 16 | public double GrandTotal { get; set; } 17 | 18 | public ICollection OrderDetails { get; set; } 19 | } 20 | 21 | public class OrderDetail 22 | { 23 | public int Id { get; set; } 24 | public int OrderId { get; set; } 25 | public Order Order { get; set; } 26 | public string ProductId { get; set; } 27 | public string ProductTitle { get; set; } 28 | public string ProductModel { get; set; } 29 | public double ProductPrice { get; set; } 30 | public int Quantity { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Online.Store.Core/DTOs/PagedTopics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Online.Store.Core.DTOs 6 | { 7 | public class PagedTopics 8 | { 9 | public List Topics { get; set; } 10 | public string ContinuationToken { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Online.Store.Core/DTOs/Post.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace Online.Store.Core.DTOs 5 | { 6 | public class Post 7 | { 8 | [JsonProperty(PropertyName = "id")] 9 | public string Id { get; set; } 10 | 11 | [JsonProperty(PropertyName = "title")] 12 | public string Title { get; set; } 13 | 14 | [JsonProperty(PropertyName = "content")] 15 | public string Content { get; set; } 16 | 17 | [JsonProperty(PropertyName = "mediaDescription")] 18 | public string MediaDescription { get; set; } 19 | 20 | [JsonProperty(PropertyName = "mediaUrl")] 21 | public string MediaUrl { get; set; } 22 | 23 | [JsonProperty(PropertyName = "mediaType")] 24 | public string MediaType { get; set; } 25 | 26 | [JsonProperty(PropertyName = "userId")] 27 | public string UserId { get; set; } 28 | 29 | [JsonProperty(PropertyName = "userName")] 30 | public string UserName { get; set; } 31 | 32 | [JsonProperty(PropertyName = "createdDate")] 33 | public DateTime CreatedDate { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Online.Store.Core/DTOs/Product.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Online.Store.Core.DTOs 6 | { 7 | public class Product : ProductInfo 8 | { 9 | public Product() 10 | { 11 | this.Components = new List(); 12 | } 13 | 14 | [JsonProperty(PropertyName="components")] 15 | public List Components { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Online.Store.Core/DTOs/ProductComponent.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Online.Store.Core.DTOs 6 | { 7 | public class ProductComponent 8 | { 9 | public ProductComponent() 10 | { 11 | this.Medias = new List(); 12 | } 13 | 14 | [JsonProperty(PropertyName = "id")] 15 | public string Id { get; set; } 16 | 17 | [JsonProperty(PropertyName="componenttype")] 18 | public string ComponentType { get; set; } 19 | 20 | [JsonProperty(PropertyName = "title")] 21 | public string ComponentTitle { get; set; } 22 | 23 | [JsonProperty(PropertyName="detail")] 24 | public string ComponentDetail { get; set; } 25 | 26 | [JsonProperty(PropertyName = "created")] 27 | public DateTime? CreatedDate { get; set; } 28 | 29 | [JsonProperty(PropertyName = "updated")] 30 | public DateTime? UpdatedDate { get; set; } 31 | 32 | [JsonProperty(PropertyName = "medias")] 33 | public List Medias { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Online.Store.Core/DTOs/ProductInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Search; 2 | using Microsoft.Azure.Search.Models; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | 8 | namespace Online.Store.Core.DTOs 9 | { 10 | public class ProductInfo 11 | { 12 | [JsonProperty(PropertyName = "id")] 13 | [System.ComponentModel.DataAnnotations.Key] 14 | [IsFilterable] 15 | public string Id { get; set; } 16 | 17 | [IsSearchable, IsFilterable, IsRetrievable(true)] 18 | [JsonProperty(PropertyName = "title")] 19 | public string Title { get; set; } 20 | 21 | [IsSearchable, IsFilterable] 22 | [JsonProperty(PropertyName = "model")] 23 | public string Model { get; set; } 24 | 25 | [JsonProperty(PropertyName = "sku")] 26 | public string SKU { get; set; } 27 | 28 | [JsonProperty(PropertyName = "price")] 29 | public double Price { get; set; } 30 | 31 | [JsonProperty(PropertyName = "image")] 32 | public string Image { get; set; } 33 | 34 | [JsonProperty(PropertyName = "description")] 35 | [IsSearchable, IsRetrievable(true)] 36 | [Analyzer(AnalyzerName.AsString.FrLucene)] 37 | public string Description { get; set; } 38 | 39 | [JsonProperty(PropertyName = "rating")] 40 | [IsSortable] 41 | public double Rating { get; set; } 42 | 43 | [JsonProperty(PropertyName = "rates")] 44 | public int Rates { get; set; } 45 | 46 | [JsonProperty(PropertyName = "created")] 47 | public DateTime? CreatedDate { get; set; } 48 | 49 | [JsonProperty(PropertyName = "updated")] 50 | public DateTime? UpdatedDate { get; set; } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Online.Store.Core/DTOs/ProductMedia.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace Online.Store.Core.DTOs 5 | { 6 | public class ProductMedia 7 | { 8 | [JsonProperty(PropertyName = "id")] 9 | public string Id { get; set; } 10 | 11 | [JsonProperty(PropertyName = "name")] 12 | public string Name { get; set; } 13 | 14 | [JsonProperty(PropertyName = "type")] 15 | public string Type { get; set; } 16 | 17 | [JsonProperty(PropertyName = "url")] 18 | public string Url { get; set; } 19 | 20 | [JsonProperty(PropertyName = "created")] 21 | public DateTime? CreatedDate { get; set; } 22 | 23 | [JsonProperty(PropertyName = "updated")] 24 | public DateTime? UpdatedDate { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Online.Store.Core/DTOs/Topic.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace Online.Store.Core.DTOs 7 | { 8 | public class Topic : Post 9 | { 10 | [JsonProperty(PropertyName = "posts")] 11 | public List Posts { get; set; } 12 | 13 | public Topic() 14 | { 15 | Posts = new List(); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Online.Store.Core/Online.Store.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Online.Store.DocumentDB/DocumentDBStoreRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Documents; 2 | using Microsoft.Azure.Documents.Client; 3 | using Microsoft.Extensions.Configuration; 4 | using Online.Store.Core.DTOs; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Online.Store.DocumentDB 12 | { 13 | public class DocumentDBStoreRepository : DocumentDBRepositoryBase 14 | { 15 | private List ConnectionPolicies; 16 | public DocumentDBStoreRepository(IConfiguration configuration) 17 | { 18 | Endpoint = string.Format("https://{0}.documents.azure.com:443/", configuration["DocumentDB:Endpoint"]); 19 | Key = configuration["DocumentDB:Key"]; 20 | DatabaseId = configuration["DocumentDB:DatabaseId"]; 21 | 22 | string policies = configuration["DocumentDB:ConnectionPolicies"]; 23 | if(!string.IsNullOrEmpty(policies)) 24 | { 25 | ConnectionPolicies = policies.Split(",").ToList(); 26 | } 27 | } 28 | 29 | public override async Task InitAsync(string collectionId) 30 | { 31 | ConnectionPolicy connectionPolicy = new ConnectionPolicy(); 32 | 33 | //Setting read region selection preference 34 | if (ConnectionPolicies != null) 35 | { 36 | foreach (var policy in ConnectionPolicies) 37 | { 38 | connectionPolicy.PreferredLocations.Add(policy); 39 | } 40 | } 41 | 42 | // Check Microsoft.Azure.Documents.LocationNames 43 | //connectionPolicy.PreferredLocations.Add(LocationNames.WestCentralUS); // first preference 44 | //connectionPolicy.PreferredLocations.Add(LocationNames.EastUS); // second preference 45 | 46 | if (_client == null) 47 | _client = new DocumentClient(new Uri(Endpoint), Key, connectionPolicy); 48 | 49 | if (CollectionId != collectionId) 50 | { 51 | CollectionId = collectionId; 52 | _collection = await _client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId)); 53 | } 54 | 55 | return _collection; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Online.Store.DocumentDB/IDocumentDBRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.Documents; 2 | using Microsoft.Azure.Documents.Client; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq.Expressions; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Online.Store.DocumentDB 10 | { 11 | public interface IDocumentDBRepository 12 | { 13 | Task GetItemAsync(string id) where T : class; 14 | 15 | Task GetItemAsync(string id, string partitionKey) where T : class; 16 | 17 | Task GetDocumentAsync(string id, string partitionKey); 18 | 19 | Task> GetItemsAsync() where T : class; 20 | 21 | Task> GetItemsAsync(Expression> predicate) where T : class; 22 | 23 | IEnumerable CreateDocumentQuery(string query, FeedOptions options) where T : class; 24 | 25 | Task> CreateDocumentQueryAsync(string query, FeedOptions options) where T : class; 26 | 27 | IEnumerable CreateDocumentQuery() where T : class; 28 | 29 | Task, string>> CreateDocumentQueryAsync(int size, 30 | string continuationToken, string query = null) where T : class; 31 | 32 | Task CreateItemAsync(T item) where T : class; 33 | 34 | Task CreateItemAsync(T item, RequestOptions options) where T : class; 35 | 36 | Task UpdateItemAsync(string id, T item) where T : class; 37 | 38 | Task> CreateAttachmentAsync(string attachmentsLink, object attachment, RequestOptions options); 39 | 40 | Task> ReadAttachmentAsync(string attachmentLink, string partitionkey); 41 | 42 | Task> ReplaceAttachmentAsync(Attachment attachment, RequestOptions options); 43 | 44 | Task DeleteItemAsync(string id); 45 | 46 | Task DeleteItemAsync(string id, string partitionKey); 47 | 48 | Task> ExecuteStoredProcedureAsync(string procedureName, string query, string partitionKey); 49 | 50 | Task InitAsync(string collectionId); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Online.Store.DocumentDB/Online.Store.DocumentDB.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Online.Store.RedisCache/IRedisCacheRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Caching.Distributed; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.RedisCache 8 | { 9 | public interface IRedisCacheRepository 10 | { 11 | Task SetStringAsync(string key, string value); 12 | Task SetStringAsync(string key, string value, int expirationMinutes); 13 | 14 | Task SetItemAsync(string key, object item); 15 | Task SetItemAsync(string key, object item, int expirationMinutes); 16 | 17 | Task GetItemAsync(string key); 18 | Task RemoveAsync(string key); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Online.Store.RedisCache/Online.Store.RedisCache.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Online.Store.RedisCache/RedisCacheReposistory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Caching.Distributed; 2 | using Microsoft.Extensions.Configuration; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Online.Store.RedisCache 10 | { 11 | public class RedisCacheReposistory : IRedisCacheRepository 12 | { 13 | private readonly IDistributedCache _distributedCache; 14 | private int _defaultExpirationMinutes; 15 | 16 | public RedisCacheReposistory(IDistributedCache distributedCache, IConfiguration configuration) 17 | { 18 | this._distributedCache = distributedCache; 19 | this._defaultExpirationMinutes = Int32.Parse(configuration["RedisCache:DefaultExpirationMinutes"]); 20 | } 21 | 22 | public async Task SetStringAsync(string key, string value) 23 | { 24 | await SetStringAsync(key, value, this._defaultExpirationMinutes); 25 | } 26 | 27 | public async Task SetStringAsync(string key, string value, int expirationMinutes) 28 | { 29 | var options = new DistributedCacheEntryOptions 30 | { 31 | AbsoluteExpiration = DateTime.Now.AddMinutes(expirationMinutes) 32 | }; 33 | 34 | await _distributedCache.SetAsync(key, Encoding.UTF8.GetBytes(value), options); 35 | } 36 | 37 | public async Task SetItemAsync(string key, object item) 38 | { 39 | string json = JsonConvert.SerializeObject(item); 40 | 41 | await SetStringAsync(key, json); 42 | } 43 | 44 | public async Task SetItemAsync(string key, object item, int expirationMinutes) 45 | { 46 | string json = JsonConvert.SerializeObject(item); 47 | 48 | await SetStringAsync(key, json, expirationMinutes); 49 | } 50 | 51 | public async Task GetItemAsync(string key) 52 | { 53 | string json = await _distributedCache.GetStringAsync(key); 54 | 55 | if (json == null) 56 | return default(T); 57 | 58 | return JsonConvert.DeserializeObject(json); 59 | } 60 | 61 | public async Task RemoveAsync(string key) 62 | { 63 | await _distributedCache.RemoveAsync(key); 64 | } 65 | 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Online.Store.ServiceBus/IServiceBusRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.ServiceBus; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.ServiceBus 8 | { 9 | public interface IServiceBusRepository 10 | { 11 | void InitQueueClient(string serviceBusAccessKeyName, string serviceBusAccessKey, string queue); 12 | Task SendAsync(object message); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Online.Store.ServiceBus/Online.Store.ServiceBus.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Online.Store.ServiceBus/ServiceBusRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Microsoft.Azure.ServiceBus; 6 | using Microsoft.Extensions.Configuration; 7 | using Newtonsoft.Json; 8 | 9 | namespace Online.Store.ServiceBus 10 | { 11 | public class ServiceBusRepository : IServiceBusRepository 12 | { 13 | IQueueClient _queueClient; 14 | IConfiguration _configuration; 15 | string _serviceBusNamespace; 16 | 17 | public ServiceBusRepository(IConfiguration configuration) 18 | { 19 | _configuration = configuration; 20 | _serviceBusNamespace = _configuration["ServiceBus:Namespace"]; 21 | } 22 | 23 | 24 | public void InitQueueClient(string serviceBusAccessKeyName, string serviceBusAccessKey, string queue) 25 | { 26 | var connectionString = 27 | $"Endpoint=sb://{_serviceBusNamespace}" + 28 | $".servicebus.windows.net/;SharedAccessKeyName={serviceBusAccessKeyName};" + 29 | $"SharedAccessKey={serviceBusAccessKey}"; 30 | 31 | _queueClient = new QueueClient(connectionString, queue); 32 | } 33 | 34 | public async Task SendAsync(object body) 35 | { 36 | var serializedBody = JsonConvert.SerializeObject(body); 37 | var message = new Message(Encoding.UTF8.GetBytes(serializedBody)); 38 | await _queueClient.SendAsync(message); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Online.Store.SqlServer/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Design; 4 | using Microsoft.Extensions.Configuration; 5 | using Online.Store.Core.DTOs; 6 | using Microsoft.EntityFrameworkCore.Metadata.Internal; 7 | 8 | namespace Online.Store.SqlServer 9 | { 10 | // Read-Write DbContext 11 | public class ApplicationDbContext : DbContext 12 | { 13 | public DbSet Orders { get; set; } 14 | 15 | public ApplicationDbContext(DbContextOptions options) 16 | : base(options) 17 | { 18 | } 19 | 20 | protected override void OnModelCreating(ModelBuilder builder) 21 | { 22 | base.OnModelCreating(builder); 23 | 24 | foreach (var entity in builder.Model.GetEntityTypes()) 25 | { 26 | entity.Relational().TableName = entity.DisplayName(); 27 | } 28 | } 29 | } 30 | 31 | public class TemporaryDbContextFactory : IDesignTimeDbContextFactory 32 | { 33 | //////// 34 | public ApplicationDbContext CreateDbContext(string[] args) 35 | { 36 | var builder = new DbContextOptionsBuilder(); 37 | IConfigurationRoot configuration = new ConfigurationBuilder() 38 | .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) 39 | .AddJsonFile("appsettings.json") 40 | .Build(); 41 | 42 | builder.UseSqlServer(configuration.GetConnectionString("DefaultConnection")); 43 | return new ApplicationDbContext(builder.Options); 44 | } 45 | 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Online.Store.SqlServer/Migrations/20171125180753_InitialCreate.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage; 7 | using Microsoft.EntityFrameworkCore.Storage.Internal; 8 | using Online.Store.SqlServer; 9 | using System; 10 | 11 | namespace Online.Store.SqlServer.Migrations 12 | { 13 | [DbContext(typeof(ApplicationDbContext))] 14 | [Migration("20171125180753_InitialCreate")] 15 | partial class InitialCreate 16 | { 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder 21 | .HasAnnotation("ProductVersion", "2.0.0-rtm-26452") 22 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 23 | 24 | modelBuilder.Entity("Online.Store.Core.DTOs.Order", b => 25 | { 26 | b.Property("Id") 27 | .ValueGeneratedOnAdd(); 28 | 29 | b.Property("DateCreated"); 30 | 31 | b.Property("UserId"); 32 | 33 | b.HasKey("Id"); 34 | 35 | b.ToTable("Order"); 36 | }); 37 | 38 | modelBuilder.Entity("Online.Store.Core.DTOs.OrderDetail", b => 39 | { 40 | b.Property("Id") 41 | .ValueGeneratedOnAdd(); 42 | 43 | b.Property("OrderId"); 44 | 45 | b.Property("ProductId"); 46 | 47 | b.Property("ProductModel"); 48 | 49 | b.Property("ProductPrice"); 50 | 51 | b.Property("ProductTitle"); 52 | 53 | b.Property("Quantity"); 54 | 55 | b.HasKey("Id"); 56 | 57 | b.HasIndex("OrderId"); 58 | 59 | b.ToTable("OrderDetail"); 60 | }); 61 | 62 | modelBuilder.Entity("Online.Store.Core.DTOs.OrderDetail", b => 63 | { 64 | b.HasOne("Online.Store.Core.DTOs.Order", "Order") 65 | .WithMany("OrderDetails") 66 | .HasForeignKey("OrderId") 67 | .OnDelete(DeleteBehavior.Cascade); 68 | }); 69 | #pragma warning restore 612, 618 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Online.Store.SqlServer/Migrations/20171129181600_add_order_grand_total.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace Online.Store.SqlServer.Migrations 6 | { 7 | public partial class add_order_grand_total : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.AddColumn( 12 | name: "GrandTotal", 13 | table: "Order", 14 | type: "float", 15 | nullable: false, 16 | defaultValue: 0.0); 17 | } 18 | 19 | protected override void Down(MigrationBuilder migrationBuilder) 20 | { 21 | migrationBuilder.DropColumn( 22 | name: "GrandTotal", 23 | table: "Order"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Online.Store.SqlServer/Migrations/ApplicationDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using Microsoft.EntityFrameworkCore.Storage; 7 | using Microsoft.EntityFrameworkCore.Storage.Internal; 8 | using Online.Store.SqlServer; 9 | using System; 10 | 11 | namespace Online.Store.SqlServer.Migrations 12 | { 13 | [DbContext(typeof(ApplicationDbContext))] 14 | partial class ApplicationDbContextModelSnapshot : ModelSnapshot 15 | { 16 | protected override void BuildModel(ModelBuilder modelBuilder) 17 | { 18 | #pragma warning disable 612, 618 19 | modelBuilder 20 | .HasAnnotation("ProductVersion", "2.0.0-rtm-26452") 21 | .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); 22 | 23 | modelBuilder.Entity("Online.Store.Core.DTOs.Order", b => 24 | { 25 | b.Property("Id") 26 | .ValueGeneratedOnAdd(); 27 | 28 | b.Property("DateCreated"); 29 | 30 | b.Property("GrandTotal"); 31 | 32 | b.Property("UserId"); 33 | 34 | b.HasKey("Id"); 35 | 36 | b.ToTable("Order"); 37 | }); 38 | 39 | modelBuilder.Entity("Online.Store.Core.DTOs.OrderDetail", b => 40 | { 41 | b.Property("Id") 42 | .ValueGeneratedOnAdd(); 43 | 44 | b.Property("OrderId"); 45 | 46 | b.Property("ProductId"); 47 | 48 | b.Property("ProductModel"); 49 | 50 | b.Property("ProductPrice"); 51 | 52 | b.Property("ProductTitle"); 53 | 54 | b.Property("Quantity"); 55 | 56 | b.HasKey("Id"); 57 | 58 | b.HasIndex("OrderId"); 59 | 60 | b.ToTable("OrderDetail"); 61 | }); 62 | 63 | modelBuilder.Entity("Online.Store.Core.DTOs.OrderDetail", b => 64 | { 65 | b.HasOne("Online.Store.Core.DTOs.Order", "Order") 66 | .WithMany("OrderDetails") 67 | .HasForeignKey("OrderId") 68 | .OnDelete(DeleteBehavior.Cascade); 69 | }); 70 | #pragma warning restore 612, 618 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Online.Store.SqlServer/Online.Store.SqlServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Online.Store.Storage/IStorageRepository.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.WindowsAzure.Storage.Blob; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | 5 | namespace Online.Store.Storage 6 | { 7 | public interface IStorageRepository 8 | { 9 | void Connect(string accountName, string accountKey); 10 | 11 | CloudBlobContainer GetBlobContainer(string containerName); 12 | 13 | Task CreateBlobContainerAsync(string containerName); 14 | 15 | Task UploadToContainerAsync(string containerName, 16 | string filePath, 17 | string blobName); 18 | 19 | Task UploadToContainerAsync(string containerName, 20 | Stream fileStream, 21 | string blobName, 22 | string contentType); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Online.Store.Storage/Online.Store.Storage.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Online.Store.Storage/StorageInitializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Storage 8 | { 9 | public class StorageInitializer 10 | { 11 | private static string accountName; 12 | private static string accountKey; 13 | public static StorageRepository _repository; 14 | 15 | public static void Initialize(IConfiguration configuration) 16 | { 17 | accountName = configuration["Storage:AccountName"]; 18 | accountKey = configuration["Storage:AccountKey"]; 19 | 20 | _repository = new StorageRepository(); 21 | _repository.Connect(accountName, accountKey); 22 | 23 | InitContainerAsync("product-images").Wait(); 24 | InitContainerAsync("forum-images").Wait(); 25 | InitContainerAsync("forum-videos").Wait(); 26 | } 27 | 28 | public static async Task InitContainerAsync(string container) 29 | { 30 | await _repository.CreateBlobContainerAsync(container); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Online.Store.WebJob/Online.Store.WebJob.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | PreserveNewest 23 | 24 | 25 | PreserveNewest 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Online.Store.WebJob/OrderService.cs: -------------------------------------------------------------------------------- 1 | using Online.Store.Core.DTOs; 2 | using Online.Store.SqlServer; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Online.Store.WebJob 9 | { 10 | public class OrderService : IOrderService 11 | { 12 | ApplicationDbContext _context; 13 | 14 | public OrderService(ApplicationDbContext context) 15 | { 16 | _context = context; 17 | } 18 | public async Task AddOrderAsync(Order order) 19 | { 20 | _context.Orders.Add(order); 21 | await _context.SaveChangesAsync(); 22 | } 23 | } 24 | 25 | public interface IOrderService 26 | { 27 | Task AddOrderAsync(Order order); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Online.Store.WebJob/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Online.Store.SqlServer; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Text; 9 | 10 | namespace Online.Store.WebJob 11 | { 12 | public class Startup 13 | { 14 | public IConfigurationRoot Configuration { get; } 15 | 16 | public Startup() 17 | { 18 | var builder = new ConfigurationBuilder() 19 | .AddJsonFile("appsettings.json"); 20 | 21 | Configuration = builder.Build(); 22 | } 23 | 24 | public void ConfigureServices(IServiceCollection services) 25 | { 26 | // Make Configuration injectable 27 | services.AddSingleton(Configuration); 28 | 29 | services.AddDbContext(options => 30 | options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); 31 | 32 | services.AddSingleton(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Online.Store.WebJob/appsettings.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "" 4 | }, 5 | "ServiceBus:Namespace": "", 6 | "ServiceBus:Queue": "orders", 7 | "ServiceBus:ReadAccessKeyName": "Read", 8 | "ServiceBus:ReadAccessKey": "" 9 | } -------------------------------------------------------------------------------- /Online.Store.WebJob/run.cmd: -------------------------------------------------------------------------------- 1 | dotnet Online.Store.WebJob.dll -------------------------------------------------------------------------------- /Online.Store/App_Data/db/migrations.sql: -------------------------------------------------------------------------------- 1 | IF OBJECT_ID(N'__EFMigrationsHistory') IS NULL 2 | BEGIN 3 | CREATE TABLE [__EFMigrationsHistory] ( 4 | [MigrationId] nvarchar(150) NOT NULL, 5 | [ProductVersion] nvarchar(32) NOT NULL, 6 | CONSTRAINT [PK___EFMigrationsHistory] PRIMARY KEY ([MigrationId]) 7 | ); 8 | END; 9 | 10 | GO 11 | 12 | IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20171125180753_InitialCreate') 13 | BEGIN 14 | CREATE TABLE [Order] ( 15 | [Id] int NOT NULL IDENTITY, 16 | [DateCreated] datetime2 NOT NULL, 17 | [UserId] nvarchar(max) NULL, 18 | CONSTRAINT [PK_Order] PRIMARY KEY ([Id]) 19 | ); 20 | END; 21 | 22 | GO 23 | 24 | IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20171125180753_InitialCreate') 25 | BEGIN 26 | CREATE TABLE [OrderDetail] ( 27 | [Id] int NOT NULL IDENTITY, 28 | [OrderId] int NOT NULL, 29 | [ProductId] nvarchar(max) NULL, 30 | [ProductModel] nvarchar(max) NULL, 31 | [ProductPrice] float NOT NULL, 32 | [ProductTitle] nvarchar(max) NULL, 33 | [Quantity] int NOT NULL, 34 | CONSTRAINT [PK_OrderDetail] PRIMARY KEY ([Id]), 35 | CONSTRAINT [FK_OrderDetail_Order_OrderId] FOREIGN KEY ([OrderId]) REFERENCES [Order] ([Id]) ON DELETE CASCADE 36 | ); 37 | END; 38 | 39 | GO 40 | 41 | IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20171125180753_InitialCreate') 42 | BEGIN 43 | CREATE INDEX [IX_OrderDetail_OrderId] ON [OrderDetail] ([OrderId]); 44 | END; 45 | 46 | GO 47 | 48 | IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20171125180753_InitialCreate') 49 | BEGIN 50 | INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) 51 | VALUES (N'20171125180753_InitialCreate', N'2.0.0-rtm-26452'); 52 | END; 53 | 54 | GO 55 | 56 | IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20171129181600_add_order_grand_total') 57 | BEGIN 58 | ALTER TABLE [Order] ADD [GrandTotal] float NOT NULL DEFAULT 0E0; 59 | END; 60 | 61 | GO 62 | 63 | IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20171129181600_add_order_grand_total') 64 | BEGIN 65 | INSERT INTO [__EFMigrationsHistory] ([MigrationId], [ProductVersion]) 66 | VALUES (N'20171129181600_add_order_grand_total', N'2.0.0-rtm-26452'); 67 | END; 68 | 69 | GO 70 | 71 | -------------------------------------------------------------------------------- /Online.Store/App_Data/devops/init-geo-replication.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Establish Active Geo-Replication between two regions. Database on the secondary region should not exist before the srcript runs 4 | https://docs.microsoft.com/en-us/azure/sql-database/scripts/sql-database-setup-geodr-and-failover-database-powershell 5 | https://docs.microsoft.com/en-us/powershell/module/azurerm.sql/new-azurermsqldatabasesecondary?view=azurermps-5.1.1 6 | 7 | .Author: Christos Sakellarios 8 | 9 | .PARAMETER Database 10 | The Database name to be geo-replicated. Database should exist on the primary region server 11 | .PARAMETER PrimaryResourceGroupName 12 | The Resource Group name where the primary SQL Server exists 13 | .PARAMETER PrimaryServerName (optional) 14 | The Primary Server's name. If null the PrimaryResourceGroupName will be used 15 | .PARAMETER SecondaryResourceGroupName 16 | The Resource Group name where the secondary SQL Server exists 17 | .PARAMETER SecondaryServerName (optional) 18 | The Secondary Server's name. If null the SecondaryResourceGroupName will be used 19 | 20 | #> 21 | param ( 22 | [Parameter(Mandatory = $true)] [string] $Database, 23 | [Parameter(Mandatory = $true)] [string] $PrimaryResourceGroupName, 24 | [Parameter(Mandatory = $true)] [string] $PrimaryServerName, 25 | [Parameter(Mandatory = $true)] [string] $SecondaryResourceGroupName, 26 | [Parameter(Mandatory = $true)] [string] $SecondaryServerName 27 | ) 28 | 29 | ECHO OFF 30 | Clear-Host 31 | 32 | Write-Host "Database: $Database" 33 | Write-Host "PrimaryResourceGroupName: $PrimaryResourceGroupName" 34 | Write-Host "PrimaryServerName: $PrimaryServerName" 35 | Write-Host "SecondaryResourceGroupName: $SecondaryResourceGroupName" 36 | Write-Host "SecondaryServerName: $SecondaryServerName" 37 | 38 | Write-Host "Setting active Geo-Replication from $PrimaryServerName to $SecondaryServerName for database $Database" 39 | 40 | $primaryDatabase = Get-AzureRmSqlDatabase -DatabaseName $Database ` 41 | -ResourceGroupName $PrimaryResourceGroupName -ServerName $PrimaryServerName 42 | 43 | $primaryDatabase | New-AzureRmSqlDatabaseSecondary -PartnerResourceGroupName "$SecondaryResourceGroupName" ` 44 | -PartnerServerName "$SecondaryServerName" -AllowConnections "All" 45 | 46 | Write-Host "Active Geo-Replication has been set succcessfully.." 47 | 48 | # Send a beep 49 | [console]::beep(1000,500) -------------------------------------------------------------------------------- /Online.Store/App_Data/devops/mac-linux/init-geo-replication.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Establish Active Geo-Replication between two regions. Database on the secondary region should not exist before the srcript runs 4 | https://docs.microsoft.com/en-us/azure/sql-database/scripts/sql-database-setup-geodr-and-failover-database-powershell 5 | https://docs.microsoft.com/en-us/powershell/module/azurerm.sql/new-azurermsqldatabasesecondary?view=azurermps-5.1.1 6 | 7 | .Author: Christos Sakellarios 8 | 9 | .PARAMETER Database 10 | The Database name to be geo-replicated. Database should exist on the primary region server 11 | .PARAMETER PrimaryResourceGroupName 12 | The Resource Group name where the primary SQL Server exists 13 | .PARAMETER PrimaryServerName (optional) 14 | The Primary Server's name. If null the PrimaryResourceGroupName will be used 15 | .PARAMETER SecondaryResourceGroupName 16 | The Resource Group name where the secondary SQL Server exists 17 | .PARAMETER SecondaryServerName (optional) 18 | The Secondary Server's name. If null the SecondaryResourceGroupName will be used 19 | 20 | #> 21 | param ( 22 | [Parameter(Mandatory = $true)] [string] $Database, 23 | [Parameter(Mandatory = $true)] [string] $PrimaryResourceGroupName, 24 | [Parameter(Mandatory = $true)] [string] $PrimaryServerName, 25 | [Parameter(Mandatory = $true)] [string] $SecondaryResourceGroupName, 26 | [Parameter(Mandatory = $true)] [string] $SecondaryServerName 27 | ) 28 | 29 | ECHO OFF 30 | Clear-Host 31 | 32 | Write-Host "Database: $Database" 33 | Write-Host "PrimaryResourceGroupName: $PrimaryResourceGroupName" 34 | Write-Host "PrimaryServerName: $PrimaryServerName" 35 | Write-Host "SecondaryResourceGroupName: $SecondaryResourceGroupName" 36 | Write-Host "SecondaryServerName: $SecondaryServerName" 37 | 38 | Write-Host "Setting active Geo-Replication from $PrimaryServerName to $SecondaryServerName for database $Database" 39 | 40 | New-AzureRmSqlDatabaseSecondary -DatabaseName $Database ` 41 | -ResourceGroupName $PrimaryResourceGroupName -ServerName $PrimaryServerName ` 42 | -PartnerResourceGroupName $SecondaryResourceGroupName ` 43 | -PartnerServerName $SecondaryServerName -AllowConnections "All" 44 | 45 | Write-Host "Active Geo-Replication has been set succcessfully.." 46 | -------------------------------------------------------------------------------- /Online.Store/App_Data/devops/mac-linux/set-geo-replication.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | # Stop or Start geo-repclication to ghost secondary databases 4 | # https://docs.microsoft.com/en-us/powershell/module/azure/stop-azuresqldatabasecopy?view=azuresmps-4.0.0 5 | # https://docs.microsoft.com/en-us/powershell/module/azure/start-azuresqldatabasecopy?view=azuresmps-4.0.0 6 | 7 | 8 | .Author: Christos Sakellarios 9 | 10 | .PARAMETER Database 11 | The Database to start or stop being geo-replicated. 12 | 13 | .PARAMETER PrimaryServerName 14 | The primary's database SQL Server 15 | 16 | .PARAMETER SecondaryServerName 17 | The secondary's database SQL Server 18 | 19 | .PARAMETER Action 20 | Start or Stop geo-replication. Possible values "Stop", "Start" 21 | 22 | #> 23 | param ( 24 | [Parameter(Mandatory = $true)] [string] $Database, 25 | [Parameter(Mandatory = $true)] [string] $PrimaryServerName, 26 | [Parameter(Mandatory = $true)] [string] $SecondaryServerName, 27 | [Parameter(Mandatory = $true)] [string] $Action 28 | ) 29 | 30 | ECHO OFF 31 | Clear-Host 32 | 33 | if($Action -eq "Stop") 34 | { 35 | Write-Host "Stopping Geo-Replication from $PrimaryServerName to $SecondaryServerName..." 36 | 37 | Stop-AzureSqlDatabaseCopy -ServerName $PrimaryServerName ` 38 | -DatabaseName $Database ` 39 | -PartnerServer $SecondaryServerName 40 | 41 | Write-Host "Geo-Replication stopped successfully..." 42 | } 43 | 44 | if($Action -eq "Start") 45 | { 46 | Write-Host "Starting Geo-Replication from $PrimaryServerName to $SecondaryServerName..." 47 | 48 | Start-AzureSqlDatabaseCopy -ServerName $PrimaryServerName ` 49 | -DatabaseName $Database ` 50 | -PartnerServer $SecondaryServerName ` 51 | -ContinuousCopy 52 | 53 | Write-Host "Geo-Replication configured successfully..." 54 | } 55 | -------------------------------------------------------------------------------- /Online.Store/App_Data/devops/set-geo-replication.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | # Stop or Start geo-repclication to ghost secondary databases 4 | # https://docs.microsoft.com/en-us/powershell/module/azure/stop-azuresqldatabasecopy?view=azuresmps-4.0.0 5 | # https://docs.microsoft.com/en-us/powershell/module/azure/start-azuresqldatabasecopy?view=azuresmps-4.0.0 6 | 7 | 8 | .Author: Christos Sakellarios 9 | 10 | .PARAMETER Database 11 | The Database to start or stop being geo-replicated. 12 | 13 | .PARAMETER PrimaryServerName 14 | The primary's database SQL Server 15 | 16 | .PARAMETER SecondaryServerName 17 | The secondary's database SQL Server 18 | 19 | .PARAMETER Action 20 | Start or Stop geo-replication. Possible values "Stop", "Start" 21 | 22 | #> 23 | param ( 24 | [Parameter(Mandatory = $true)] [string] $Database, 25 | [Parameter(Mandatory = $true)] [string] $PrimaryServerName, 26 | [Parameter(Mandatory = $true)] [string] $SecondaryServerName, 27 | [Parameter(Mandatory = $true)] [string] $Action 28 | ) 29 | 30 | ECHO OFF 31 | Clear-Host 32 | 33 | if($Action -eq "Stop") 34 | { 35 | Write-Host "Stopping Geo-Replication from $PrimaryServerName to $SecondaryServerName..." 36 | 37 | Stop-AzureSqlDatabaseCopy -ServerName $PrimaryServerName ` 38 | -DatabaseName $Database ` 39 | -PartnerServer $SecondaryServerName 40 | 41 | Write-Host "Geo-Replication stopped successfully..." 42 | } 43 | 44 | if($Action -eq "Start") 45 | { 46 | Write-Host "Starting Geo-Replication from $PrimaryServerName to $SecondaryServerName..." 47 | 48 | Start-AzureSqlDatabaseCopy -ServerName $PrimaryServerName ` 49 | -DatabaseName $Database ` 50 | -PartnerServer $SecondaryServerName ` 51 | -ContinuousCopy 52 | 53 | Write-Host "Geo-Replication configured successfully..." 54 | } 55 | 56 | # Send a beep 57 | [console]::beep(1000,500) -------------------------------------------------------------------------------- /Online.Store/App_Data/devops/set-traffic-manager-endpoint.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Enable or disable Traffic Manager Endpoint 4 | 5 | .Author: Christos Sakellarios 6 | 7 | .PARAMETER EndpointName 8 | App Service name 9 | 10 | .PARAMETER TraficManagerProfile 11 | Traffic Manager Profile Name 12 | 13 | .PARAMETER ResourceGroupName 14 | Traffic Manager Profile resouroce group 15 | 16 | .PARAMETER Enable 17 | Enable or Disable endpoint. 18 | 19 | e.g. .\set-traffic-manager-endpoint.ps1 -EndpointName "" ` 20 | -TraficManagerProfile "" ` 21 | -ResourceGroupName "" ` 22 | -Enable $true 23 | #> 24 | 25 | param ( 26 | [Parameter(Mandatory = $true)] [string] $EndpointName, 27 | [Parameter(Mandatory = $true)] [string] $TraficManagerProfile, 28 | [Parameter(Mandatory = $true)] [string] $ResourceGroupName, 29 | [Parameter(Mandatory = $true)] [bool] $Enable 30 | ) 31 | 32 | Clear-Host 33 | 34 | # Enable disabled endpoints 35 | 36 | if($Enable) { 37 | 38 | Write-Host "Enabling $EndpointName..."; 39 | 40 | Enable-AzureRmTrafficManagerEndpoint -Name $EndpointName -Type AzureEndpoints ` 41 | -ProfileName $TraficManagerProfile ` 42 | -ResourceGroupName $ResourceGroupName 43 | } 44 | else { 45 | 46 | Write-Host "Disabling $EndpointName..."; 47 | 48 | Disable-AzureRmTrafficManagerEndpoint -Name $EndpointName -Type AzureEndpoints ` 49 | -ProfileName $TraficManagerProfile ` 50 | -ResourceGroupName $ResourceGroupName 51 | 52 | } 53 | 54 | # Send a beep 55 | [console]::beep(1000,500) -------------------------------------------------------------------------------- /Online.Store/App_Data/templates/secrets.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "DocumentDB:Key": "", 3 | "DocumentDB:ConnectionPolicies": "", 4 | "SearchService:ApiKey": "", 5 | "Storage:AccountKey": "", 6 | "MediaServices:AccountKey": "", 7 | "ConnectionStrings:DefaultConnection": "", 8 | "ConnectionStrings:IdentityConnection": "", 9 | "RedisCache:Key": "", 10 | "AzureAd:ClientSecret": "", 11 | "ServiceBus:WriteAccessKeyName": "Write", 12 | "ServiceBus:WriteAccessKey": "" 13 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/app.component.css: -------------------------------------------------------------------------------- 1 | .row-content { 2 | margin-top: 51px; 3 | background-color: whitesmoke; 4 | } 5 | 6 | .backdrop { 7 | height: 100%; 8 | } 9 | 10 | @media (max-width: 767px) { 11 | /* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */ 12 | .body-content { 13 | padding-top: 50px; 14 | } 15 | .row-content { 16 | margin-top: 0px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NavigationEnd, Router, Event } from '@angular/router'; 3 | import { Store } from '@ngrx/store'; 4 | import { UserState } from './user/store/user.state'; 5 | import * as userActions from './user/store/user.actions'; 6 | import { Observable } from 'rxjs/Observable'; 7 | import { Cart } from './models/cart'; 8 | import { User } from './models/user'; 9 | import { NotifyService } from './core/services/notifications.service'; 10 | import { Message, MessageType } from './models/message'; 11 | import { ISubscription } from 'rxjs/Subscription'; 12 | 13 | @Component({ 14 | selector: 'app', 15 | templateUrl: './app.component.html', 16 | styleUrls: ['./app.component.css'] 17 | }) 18 | export class AppComponent implements OnInit { 19 | 20 | private subscription: ISubscription; 21 | 22 | cart$: Observable; 23 | user$: Observable; 24 | firstRouteEnd: boolean = false; 25 | 26 | public options = { 27 | position: ["bottom", "left"], 28 | timeOut: 5000, 29 | lastOnBottom: true, 30 | showProgressBar: true 31 | } 32 | 33 | public loading = false; 34 | 35 | constructor(private store: Store, public notifyService: NotifyService, private router: Router) { 36 | this.cart$ = this.store.select(state => state.user.userState.cart); 37 | this.user$ = this.store.select(state => state.user.userState.user); 38 | } 39 | 40 | ngOnInit(): void { 41 | const self = this; 42 | 43 | this.router.events.subscribe((event: Event) => { 44 | if (event instanceof NavigationEnd) { 45 | if (!self.firstRouteEnd) { 46 | self.firstRouteEnd = true; 47 | self.store.dispatch(new userActions.GetCartAction()); 48 | } 49 | } 50 | }); 51 | 52 | this.subscription = this.notifyService.loading$.subscribe(loading => { 53 | setTimeout(function () { 54 | self.loading = loading; 55 | }, 100); 56 | }); 57 | } 58 | 59 | ngOnDestroy() { 60 | this.subscription.unsubscribe(); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/app.constants.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class Configuration { 5 | public Server = 'http://localhost:57276/'; 6 | } 7 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/app.module.browser.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { AppModuleShared } from './app.module.shared'; 4 | import { AppComponent } from './app.component'; 5 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 6 | 7 | @NgModule({ 8 | bootstrap: [ AppComponent ], 9 | imports: [ 10 | BrowserModule, 11 | BrowserAnimationsModule, 12 | AppModuleShared 13 | ], 14 | providers: [ 15 | { provide: 'BASE_URL', useFactory: getBaseUrl } 16 | ] 17 | }) 18 | export class AppModule { 19 | } 20 | 21 | export function getBaseUrl() { 22 | return document.getElementsByTagName('base')[0].href; 23 | } 24 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/app.module.server.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ServerModule } from '@angular/platform-server'; 3 | import { AppModuleShared } from './app.module.shared'; 4 | import { AppComponent } from './app.component'; 5 | 6 | @NgModule({ 7 | bootstrap: [ AppComponent ], 8 | imports: [ 9 | ServerModule, 10 | AppModuleShared 11 | ] 12 | }) 13 | export class AppModule { 14 | } 15 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/app.module.shared.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpModule } from '@angular/http'; 5 | import { RouterModule } from '@angular/router'; 6 | 7 | import { AppComponent } from './app.component'; 8 | import { CoreModule } from './core/core.module'; 9 | 10 | import { NavMenuComponent } from './navmenu/navmenu.component'; 11 | import { NavBarComponent } from './navmenu/navbar.component'; 12 | import { HomeComponent } from './home/home.component'; 13 | import { CheckoutComponent } from "./checkout/checkout.component"; 14 | import { CheckoutPresentationComponent } from "./checkout/checkout"; 15 | import { OrdersComponent } from './orders/orders.component'; 16 | import { OrdersPresentationComponent } from './orders/orders'; 17 | 18 | import { AppRoutes } from './app.routes'; 19 | import { ProductModule } from './product/product.module'; 20 | 21 | import { EffectsModule } from '@ngrx/effects'; 22 | import { StoreModule } from '@ngrx/store'; 23 | import { StoreDevtoolsModule } from '@ngrx/store-devtools'; 24 | import { reducers } from "./reducers"; 25 | 26 | import { LoadingModule, ANIMATION_TYPES } from 'ngx-loading'; 27 | import { Ng2CompleterModule } from "ng2-completer"; 28 | import { SimpleNotificationsModule } from 'angular2-notifications'; 29 | import { ImgFallbackModule } from 'ngx-img-fallback'; 30 | 31 | @NgModule({ 32 | declarations: [ 33 | AppComponent, 34 | NavMenuComponent, 35 | NavBarComponent, 36 | CheckoutComponent, 37 | CheckoutPresentationComponent, 38 | OrdersComponent, 39 | OrdersPresentationComponent, 40 | HomeComponent 41 | ], 42 | imports: [ 43 | CommonModule, 44 | HttpModule, 45 | FormsModule, 46 | AppRoutes, 47 | ImgFallbackModule, 48 | CoreModule.forRoot(), 49 | LoadingModule.forRoot({ 50 | animationType: ANIMATION_TYPES.threeBounce, 51 | backdropBackgroundColour: 'rgba(0,0,0,0.1)', 52 | backdropBorderRadius: '4px', 53 | primaryColour: 'rgb(65, 137, 199)', 54 | secondaryColour: 'rgb(65, 137, 199)', 55 | tertiaryColour: 'rgb(65, 137, 199)', 56 | fullScreenBackdrop: true 57 | }), 58 | Ng2CompleterModule, 59 | SimpleNotificationsModule.forRoot(), 60 | StoreModule.forRoot(reducers), 61 | StoreDevtoolsModule.instrument({ 62 | maxAge: 5 // Retains last 25 states 63 | }), 64 | EffectsModule.forRoot([]) 65 | ] 66 | }) 67 | export class AppModuleShared { 68 | } 69 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | import { HomeComponent } from "./home/home.component"; 3 | import { CheckoutComponent } from "./checkout/checkout.component"; 4 | import { UserComponent } from './user/user.component'; 5 | import { OrdersComponent } from './orders/orders.component'; 6 | 7 | export const routes: Routes = [ 8 | { path: '', redirectTo: 'home', pathMatch: 'full' }, 9 | { path: 'home', component: HomeComponent }, 10 | { path: 'products', loadChildren: './product/product.module#ProductModule' }, 11 | { path: 'forum', loadChildren: './forum/forum.module#ForumModule' }, 12 | { path: 'checkout', component: CheckoutComponent }, 13 | { path: 'orders', component: OrdersComponent }, 14 | { path: '**', redirectTo: 'home' } 15 | ]; 16 | 17 | export const AppRoutes = RouterModule.forRoot(routes); 18 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/checkout/checkout.component.html: -------------------------------------------------------------------------------- 1 |  3 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/checkout/checkout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Store } from '@ngrx/store'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { Cart } from "../models/cart"; 5 | import * as userActions from '../user/store/user.actions'; 6 | import { NotifyService } from '../core/services/notifications.service'; 7 | import { Message, MessageType } from '../models/message'; 8 | import { ISubscription } from 'rxjs/Subscription'; 9 | import { Subject, BehaviorSubject } from 'rxjs'; 10 | import { User } from '../models/user'; 11 | 12 | @Component({ 13 | selector: 'checkout-order', 14 | templateUrl: './checkout.component.html', 15 | 16 | }) 17 | 18 | export class CheckoutComponent implements OnInit { 19 | 20 | cart$: Observable; 21 | cartTotal$: Observable; 22 | user$: Observable; 23 | 24 | constructor(private store: Store, private notifyService: NotifyService) { 25 | this.cart$ = this.store.select(state => state.user.userState.cart); 26 | this.cartTotal$ = this.store.select(state => state.user.userState.cartTotal); 27 | this.user$ = this.store.select(state => state.user.userState.user); 28 | } 29 | 30 | ngOnInit() { 31 | } 32 | 33 | completeOrder(id: string) { 34 | this.displayMessage('Processing request..'); 35 | this.store.dispatch(new userActions.CompleteOrderAction(id)); 36 | } 37 | 38 | removeProduct(productId: string) { 39 | this.store.dispatch(new userActions.RemoveProductFromCartAction(productId)); 40 | } 41 | 42 | displayMessage(message: string) { 43 | const notification: Message = { type: MessageType.Info, message: message }; 44 | this.notifyService.setMessage(notification); 45 | } 46 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/checkout/checkout.html: -------------------------------------------------------------------------------- 1 | 
2 | 3 | Sign In to Checkout 4 | 5 | 6 |
7 |

Shopping Cart

8 |
9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 | 25 |

{{product.description}}

26 |
27 |
{{product.price | currency:'USD':true:'2.2-4'}}
28 |
{{product.quantity}}
29 |
30 | 33 |
34 |
{{product.price * product.quantity | currency:'USD':true:'2.2-4'}}
35 |
36 | 37 |
38 |
39 | 40 |
{{total | currency:'USD':true:'2.2-4'}}
41 |
42 |
43 | 44 | 45 | 46 |
-------------------------------------------------------------------------------- /Online.Store/ClientApp/app/checkout/checkout.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Cart } from "../models/cart"; 3 | import { User } from '../models/user'; 4 | 5 | @Component({ 6 | selector: 'checkout-presentation', 7 | templateUrl: './checkout.html', 8 | styleUrls: ['./checkout.css'], 9 | changeDetection: ChangeDetectionStrategy.OnPush 10 | }) 11 | 12 | export class CheckoutPresentationComponent { 13 | _cart: Cart; 14 | 15 | @Input() total: number; 16 | @Input() user: User; 17 | 18 | @Input() 19 | set cart(value: Cart) { 20 | this._cart = Object.assign({}, value); 21 | } 22 | 23 | get cart() { 24 | return this._cart; 25 | } 26 | 27 | @Output() checkout: EventEmitter = new EventEmitter(); 28 | @Output() onRemoveProduct: EventEmitter = new EventEmitter(); 29 | 30 | constructor() { } 31 | 32 | completeOrder() { 33 | console.log(this.cart.id); 34 | this.checkout.emit(this.cart.id); 35 | } 36 | 37 | remove(id: string) { 38 | this.onRemoveProduct.emit(id); 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { ModuleWithProviders, NgModule } from '@angular/core'; 3 | 4 | import { Configuration } from '../app.constants'; 5 | import { ProductService } from './services/product.service'; 6 | import { UserModule } from "../user/user.module"; 7 | import { AccountService } from './services/account.service'; 8 | import { NotificationsModule } from '../notifications/notifications.module'; 9 | import { ForumService } from './services/forum.service'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | UserModule, 15 | ], 16 | exports: [ 17 | CommonModule, 18 | UserModule, 19 | NotificationsModule, 20 | ] 21 | }) 22 | 23 | export class CoreModule { 24 | static forRoot(): ModuleWithProviders { 25 | return { 26 | ngModule: CoreModule, 27 | providers: [ 28 | ProductService, 29 | AccountService, 30 | ForumService, 31 | Configuration 32 | ] 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/core/services/account.service.ts: -------------------------------------------------------------------------------- 1 | import 'rxjs/add/operator/map'; 2 | 3 | import { Http, Response, Headers, RequestOptions } from '@angular/http'; 4 | import { Injectable, Inject } from '@angular/core'; 5 | import { Observable } from 'rxjs/Observable'; 6 | 7 | import { Configuration } from './../../app.constants'; 8 | 9 | // Import RxJs required methods 10 | import 'rxjs/add/operator/map'; 11 | import 'rxjs/add/operator/catch'; 12 | import { RegisterVM } from '../../models/register-vm'; 13 | import { LoginVM } from '../../models/login-vm'; 14 | import { ResultVM } from '../../models/result-vm'; 15 | 16 | @Injectable() 17 | export class AccountService { 18 | 19 | private accountURI: string; 20 | private identityURI: string; 21 | private headers: Headers; 22 | private requestOptions: RequestOptions; 23 | 24 | constructor(private http: Http, @Inject('BASE_URL') baseUrl: string) { 25 | 26 | this.accountURI = baseUrl + 'account/'; 27 | this.identityURI = baseUrl + 'api/identity/'; 28 | 29 | this.headers = new Headers(); 30 | this.headers.append('Content-Type', 'application/json'); 31 | this.headers.append('Accept', 'application/json'); 32 | 33 | this.requestOptions = new RequestOptions({ headers: this.headers }) 34 | } 35 | 36 | registerUser(user: RegisterVM): Observable { 37 | let url = user.useIdentity === true ? this.identityURI : this.accountURI; 38 | return this.http.post(url + 'register', JSON.stringify(user), this.requestOptions) 39 | .map((res: Response) => res.json()) 40 | .catch((error: any) => Observable.throw(error.json().error || 'Server error')); 41 | } 42 | 43 | loginUser(user: LoginVM): Observable { 44 | return this.http.post(this.identityURI + 'login', JSON.stringify(user), this.requestOptions) 45 | .map((res: Response) => res.json()) 46 | .catch((error: any) => Observable.throw(error.json().error || 'Server error')); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/core/services/notifications.service.ts: -------------------------------------------------------------------------------- 1 | import 'rxjs/add/operator/map'; 2 | 3 | import { Injectable } from '@angular/core'; 4 | import { Observable } from 'rxjs/Observable'; 5 | 6 | // Import RxJs required methods 7 | import 'rxjs/add/operator/map'; 8 | import 'rxjs/add/operator/catch'; 9 | import { NotificationsService } from 'angular2-notifications'; 10 | import { Store } from '@ngrx/store'; 11 | import { ISubscription } from 'rxjs/Subscription'; 12 | import * as notifyActions from '../../notifications/store/notifications.actions'; 13 | import { Message, MessageType } from '../../models/message'; 14 | 15 | @Injectable() 16 | export class NotifyService { 17 | 18 | loading$: Observable; 19 | message$: Observable; 20 | private subscription: ISubscription; 21 | 22 | constructor(private store: Store, private _service: NotificationsService) { 23 | 24 | this.loading$ = this.store.select(state => state.notifications.notificationsState.loading); 25 | this.message$ = this.store.select(state => state.notifications.notificationsState.message); 26 | 27 | this.message$ 28 | .skip(1) 29 | .filter(message => message.message !== null && message.message !== undefined) 30 | .subscribe((notification) => { 31 | switch (notification.type) { 32 | case MessageType.SUCCESS: 33 | this._service.success('Success', notification.message); 34 | break; 35 | case MessageType.Error: 36 | this._service.html(notification.message, 'error'); 37 | break; 38 | default: 39 | this._service.info('Info', notification.message); 40 | break; 41 | } 42 | }); 43 | } 44 | 45 | setLoading(loading: boolean) { 46 | this.store.dispatch(new notifyActions.SetLoadingAction(loading)); 47 | } 48 | 49 | setMessage(message: Message) { 50 | this.store.dispatch(new notifyActions.SetMessageAction(message)); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/forum/details/details.css: -------------------------------------------------------------------------------- 1 |  2 | .page-header { 3 | text-align: center; 4 | } 5 | 6 | .bs-callout { 7 | -moz-border-bottom-colors: none; 8 | -moz-border-left-colors: none; 9 | -moz-border-right-colors: none; 10 | -moz-border-top-colors: none; 11 | border-color: #eee; 12 | border-image: none; 13 | border-radius: 3px; 14 | border-style: solid; 15 | border-width: 1px 1px 1px 5px; 16 | margin-bottom: 5px; 17 | padding: 20px; 18 | } 19 | 20 | .bs-callout:last-child { 21 | margin-bottom: 0px; 22 | } 23 | 24 | .bs-callout h4 { 25 | margin-bottom: 10px; 26 | margin-top: 0; 27 | } 28 | 29 | .topic { 30 | border-left-color: #d9534f; 31 | } 32 | 33 | .topic h4 { 34 | color: #d9534f; 35 | } 36 | 37 | .topic-reply { 38 | border-left-color: #4189c7; 39 | background-color: whitesmoke; 40 | } 41 | 42 | .topic-reply h4 { 43 | color: #4189c7; 44 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/forum/details/details.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter, ViewChild, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Topic } from "../../models/topic"; 3 | import { Post } from '../../models/post'; 4 | import { Reply } from '../../models/reply'; 5 | import { User } from '../../models/user'; 6 | 7 | @Component({ 8 | selector: 'topic-details-presentation', 9 | templateUrl: './details.html', 10 | styleUrls: ['./details.css'], 11 | changeDetection: ChangeDetectionStrategy.OnPush 12 | }) 13 | 14 | export class TopicDetailsPresentationComponent { 15 | 16 | @Input() topic: Topic; 17 | @Input() user: User; 18 | @Output() onReply: EventEmitter = new EventEmitter(); 19 | 20 | @ViewChild("fileInput") fileInput: any; 21 | 22 | viewReply: boolean = false; 23 | title: string = ''; 24 | content: string = ''; 25 | mediaDescription: string = ''; 26 | 27 | constructor() { } 28 | 29 | addPost(): void { 30 | let fileToUpload; 31 | let fi = this.fileInput.nativeElement; 32 | 33 | if (fi.files && fi.files[0]) { 34 | fileToUpload = fi.files[0]; 35 | } 36 | 37 | const reply: Reply = new Reply(); 38 | 39 | reply.replyToPostId = this.topic.id; 40 | reply.title = this.title; 41 | reply.content = this.content; 42 | reply.mediaDescription = this.mediaDescription; 43 | reply.userId = this.user.id; 44 | reply.userName = this.user.username; 45 | 46 | if (fileToUpload) { 47 | reply.mediaFile = fileToUpload; 48 | } 49 | 50 | this.cleanReplyForm(); 51 | 52 | this.onReply.emit(reply); 53 | } 54 | 55 | cleanReplyForm() { 56 | this.viewReply = false; 57 | this.title = ''; 58 | this.content = ''; 59 | this.mediaDescription = ''; 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/forum/details/topic-details.component.html: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/forum/details/topic-details.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Store } from '@ngrx/store'; 3 | import { ForumState } from '../store/forum.state'; 4 | import { Router, ActivatedRoute, ParamMap, NavigationEnd } from '@angular/router'; 5 | import { Observable } from 'rxjs/Observable'; 6 | import { Topic } from "../../models/topic"; 7 | import * as forumActions from '../store/forum.actions'; 8 | import { Reply } from '../../models/reply'; 9 | import { User } from '../../models/user'; 10 | import { NotifyService } from '../../core/services/notifications.service'; 11 | 12 | @Component({ 13 | selector: 'topic-details', 14 | templateUrl: './topic-details.component.html' 15 | }) 16 | 17 | export class TopicDetailsComponent implements OnInit { 18 | 19 | topic$: Observable; 20 | user$: Observable; 21 | 22 | constructor(private store: Store, private notifyService: NotifyService, 23 | private route: ActivatedRoute, private router: Router) { 24 | this.topic$ = this.store.select(state => state.community.forumState.selectedTopic); 25 | this.user$ = this.store.select(state => state.user.userState.user); 26 | } 27 | 28 | ngOnInit() { 29 | this.route.paramMap 30 | .subscribe((params: ParamMap) => { 31 | this.notifyService.setLoading(true); 32 | this.store.dispatch(new forumActions.SelectTopicAction(params.get('id') || '') 33 | ) 34 | }); 35 | } 36 | 37 | submitReply(reply: Reply) { 38 | this.notifyService.setLoading(true); 39 | this.store.dispatch(new forumActions.AddReplyAction(reply)); 40 | } 41 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/forum/forum.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Store } from '@ngrx/store'; 3 | import { Observable } from 'rxjs/Observable'; 4 | 5 | @Component({ 6 | selector: 'forum-view', 7 | template: '' 8 | }) 9 | 10 | export class ForumComponent implements OnInit { 11 | 12 | 13 | constructor() { } 14 | 15 | ngOnInit() { } 16 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/forum/forum.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { TopicListComponent } from './list/topic-list.component'; 4 | import { TopicDetailsComponent } from './details/topic-details.component'; 5 | import { FORUM_ROUTES } from './forum.routes'; 6 | import { StoreModule } from '@ngrx/store'; 7 | import { EffectsModule } from '@ngrx/effects'; 8 | import { forumReducer } from './store/forum.reducer'; 9 | import { ForumEffects } from './store/forum.effects'; 10 | import { FormsModule } from '@angular/forms'; 11 | import { ForumComponent } from "./forum.component"; 12 | import { TopicListPresentationComponent } from './list/list'; 13 | import { TopicDetailsPresentationComponent } from './details/details'; 14 | 15 | const FORUM_DIRECTIVES = [ 16 | TopicListComponent, 17 | TopicListPresentationComponent, 18 | TopicDetailsComponent, 19 | TopicDetailsPresentationComponent, 20 | ForumComponent 21 | ]; 22 | 23 | @NgModule({ 24 | imports: [ 25 | CommonModule, 26 | FormsModule, 27 | FORUM_ROUTES, 28 | StoreModule.forFeature('community', { 29 | forumState: forumReducer, 30 | }), 31 | EffectsModule.forFeature([ForumEffects]) 32 | ], 33 | exports: [ 34 | ...FORUM_DIRECTIVES 35 | ], 36 | declarations: [ 37 | ...FORUM_DIRECTIVES 38 | ], 39 | providers: [], 40 | }) 41 | export class ForumModule { } 42 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/forum/forum.routes.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | 3 | import { TopicDetailsComponent } from './details/topic-details.component'; 4 | import { TopicListComponent } from './list/topic-list.component'; 5 | import { ForumComponent } from "./forum.component"; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', component: ForumComponent, children: [ 10 | { path: '', redirectTo: 'topics', pathMatch: 'full' }, 11 | { path: 'topics', component: TopicListComponent }, 12 | { path: 'topics/:id', component: TopicDetailsComponent } 13 | ] 14 | } 15 | ]; 16 | 17 | export const FORUM_ROUTES = RouterModule.forChild(routes); 18 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/forum/list/list.css: -------------------------------------------------------------------------------- 1 | .padding10 { 2 | padding: 10px; 3 | } 4 | .page-header { 5 | text-align: center; 6 | } 7 | 8 | .replies-counter { 9 | text-align: right; 10 | } 11 | 12 | /* resume stuff */ 13 | 14 | .topic { 15 | -moz-border-bottom-colors: none; 16 | -moz-border-left-colors: none; 17 | -moz-border-right-colors: none; 18 | -moz-border-top-colors: none; 19 | border-color: #eee; 20 | border-image: none; 21 | border-radius: 3px; 22 | border-style: solid; 23 | border-width: 1px 1px 1px 5px; 24 | margin-bottom: 5px; 25 | padding: 20px; 26 | } 27 | 28 | .topic:last-child { 29 | margin-bottom: 0px; 30 | } 31 | 32 | .topic h4 { 33 | margin-bottom: 10px; 34 | margin-top: 0; 35 | } 36 | 37 | .topic-danger { 38 | border-left-color: #d9534f; 39 | } 40 | 41 | .topic-danger h4 { 42 | color: #d9534f; 43 | } 44 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/forum/list/list.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy, ViewChild } from '@angular/core'; 2 | import { Topic } from "../../models/topic"; 3 | import { Reply } from '../../models/reply'; 4 | import { User } from '../../models/user'; 5 | 6 | @Component({ 7 | selector: 'topic-list-presentation', 8 | templateUrl: './list.html', 9 | styleUrls: ['./list.css'], 10 | changeDetection: ChangeDetectionStrategy.OnPush 11 | }) 12 | 13 | export class TopicListPresentationComponent { 14 | 15 | @Input() topics: Topic[]; 16 | @Input() nextToken: string; 17 | @Input() currentPage: number; 18 | @Input() totalPages: number; 19 | @Input() loading: boolean; 20 | @Input() user: User; 21 | @Output() onCreateTopic: EventEmitter = new EventEmitter(); 22 | @Output() onNext: EventEmitter = new EventEmitter(); 23 | @Output() onPrevious: EventEmitter = new EventEmitter(); 24 | 25 | @ViewChild("fileInput") fileInput: any; 26 | viewCreateTopic: boolean = false; 27 | title: string = ''; 28 | content: string = ''; 29 | mediaDescription: string = ''; 30 | 31 | constructor() { } 32 | 33 | addPost(): void { 34 | let fileToUpload; 35 | let fi = this.fileInput.nativeElement; 36 | 37 | if (fi.files && fi.files[0]) { 38 | fileToUpload = fi.files[0]; 39 | } 40 | 41 | const reply: Reply = new Reply(); 42 | 43 | reply.title = this.title; 44 | reply.content = this.content; 45 | reply.mediaDescription = this.mediaDescription; 46 | reply.userId = this.user.id; 47 | reply.userName = this.user.username; 48 | 49 | if (fileToUpload) { 50 | reply.mediaFile = fileToUpload; 51 | } 52 | 53 | this.cleanReplyForm(); 54 | 55 | this.onCreateTopic.emit(reply); 56 | } 57 | 58 | cleanReplyForm() { 59 | this.viewCreateTopic = false; 60 | this.title = ''; 61 | this.content = ''; 62 | this.mediaDescription = ''; 63 | } 64 | 65 | getNext() { 66 | this.onNext.emit( 67 | { 68 | token: this.nextToken, 69 | page: this.currentPage + 1 70 | } 71 | ); 72 | } 73 | 74 | getPrevious() { 75 | this.onPrevious.emit(this.currentPage - 1); 76 | } 77 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/forum/list/topic-list.component.html: -------------------------------------------------------------------------------- 1 |  5 | 6 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/forum/store/forum.actions.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | import { Topic } from './../../models/topic'; 3 | import { Reply } from '../../models/reply'; 4 | import { ResultVM } from '../../models/result-vm'; 5 | import { PagedTopics } from '../../models/paged-topics'; 6 | 7 | export const SELECTALL = '[Forum] Select All Topics'; 8 | export const SELECTALL_COMPLETE = '[Forum] Select All Topics Complete'; 9 | export const SET_SELECTED_PAGE = '[Forum] Set Selected Page'; 10 | export const SELECT_TOPIC = '[Forum] Select Topic'; 11 | export const SELECT_TOPIC_COMPLETE = '[Forum] Select Topic Complete'; 12 | export const ADD_REPLY = '[Forum] Add Reply'; 13 | export const ADD_TOPIC = '[Forum] Add Topic'; 14 | 15 | export class SelectAllAction implements Action { 16 | readonly type = SELECTALL; 17 | 18 | constructor(public token?: string) { } 19 | } 20 | 21 | export class SelectAllCompleteAction implements Action { 22 | readonly type = SELECTALL_COMPLETE; 23 | 24 | constructor(public topics: PagedTopics) { } 25 | } 26 | 27 | export class SetSelectedPageAction implements Action { 28 | readonly type = SET_SELECTED_PAGE; 29 | 30 | constructor(public page: number) { } 31 | } 32 | 33 | export class SelectTopicAction implements Action { 34 | readonly type = SELECT_TOPIC; 35 | 36 | constructor(public id: string) { } 37 | } 38 | 39 | export class SelectTopicCompleteAction implements Action { 40 | readonly type = SELECT_TOPIC_COMPLETE; 41 | 42 | constructor(public topic: Topic) { } 43 | } 44 | 45 | export class AddReplyAction implements Action { 46 | readonly type = ADD_REPLY; 47 | 48 | constructor(public reply: Reply) { } 49 | } 50 | 51 | export class AddTopicAction implements Action { 52 | readonly type = ADD_TOPIC; 53 | 54 | constructor(public topic: Reply) { } 55 | } 56 | 57 | export type Actions 58 | = SelectAllAction 59 | | SelectAllCompleteAction 60 | | SetSelectedPageAction 61 | | SelectTopicAction 62 | | SelectTopicCompleteAction 63 | | AddReplyAction 64 | | AddTopicAction; 65 | 66 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/forum/store/forum.reducer.ts: -------------------------------------------------------------------------------- 1 | import { ForumState } from './forum.state'; 2 | import { Topic } from './../../models/topic'; 3 | import { Action } from '@ngrx/store'; 4 | import * as forumAction from './forum.actions'; 5 | 6 | export const initialState: ForumState = { 7 | topics: new Map(), 8 | selectedPage: 0, 9 | continuationToken: undefined, 10 | selectedTopic: undefined 11 | }; 12 | 13 | export function forumReducer(state = initialState, action: forumAction.Actions): ForumState { 14 | switch (action.type) { 15 | 16 | case forumAction.SELECTALL_COMPLETE: 17 | let currentMap = new Map(state.topics); 18 | currentMap.set(state.selectedPage + 1, action.topics.topics); 19 | return Object.assign({}, state, { 20 | selectedPage: state.selectedPage + 1, 21 | continuationToken: action.topics.continuationToken, 22 | topics: currentMap 23 | }); 24 | 25 | case forumAction.SELECT_TOPIC_COMPLETE: 26 | return Object.assign({}, state, { 27 | selectedTopic: action.topic 28 | }); 29 | 30 | case forumAction.SET_SELECTED_PAGE: 31 | return Object.assign({}, state, { 32 | selectedPage: action.page 33 | }); 34 | 35 | default: 36 | return state; 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/forum/store/forum.state.ts: -------------------------------------------------------------------------------- 1 | import { Topic } from './../../models/topic'; 2 | import { Post } from "../../models/post"; 3 | 4 | export interface ForumState { 5 | topics: Map, 6 | selectedPage: number; 7 | continuationToken?: string, 8 | selectedTopic?: Topic 9 | }; -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.css'] 7 | }) 8 | export class HomeComponent { 9 | } 10 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/home/home.css: -------------------------------------------------------------------------------- 1 | .thumbnail { 2 | pointer-events: none; 3 | cursor: default; 4 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/cart-item.ts: -------------------------------------------------------------------------------- 1 | export class CartItem { 2 | public id: string; 3 | public quantity: number; 4 | public title: string; 5 | public model: string; 6 | public image: string; 7 | public price: number; 8 | public description: string; 9 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/cart.ts: -------------------------------------------------------------------------------- 1 |  2 | import { CartItem } from "./cart-item"; 3 | 4 | export class Cart { 5 | public id: string; 6 | public items: CartItem[]; 7 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/login-vm.ts: -------------------------------------------------------------------------------- 1 | export class LoginVM { 2 | public username: string; 3 | public password: string; 4 | public rememberMe: boolean; 5 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/message.ts: -------------------------------------------------------------------------------- 1 | export class Message { 2 | public type: MessageType; 3 | public message: string; 4 | } 5 | 6 | export enum MessageType { 7 | SUCCESS = 1, 8 | Error = 2, 9 | Info = 3 10 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/order.ts: -------------------------------------------------------------------------------- 1 | export class Order { 2 | public id: number; 3 | public userId: string; 4 | public dateCreated: Date; 5 | public grandTotal: number; 6 | public orderDetails: OrderDetail[]; 7 | } 8 | 9 | export class OrderDetail { 10 | public id: number; 11 | public productId: string; 12 | public productTitle: string; 13 | public productPrice: number; 14 | public productImage: string; 15 | public productCdnImage: string; 16 | public quantity: number; 17 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/paged-topics.ts: -------------------------------------------------------------------------------- 1 | import { Topic } from "./topic"; 2 | 3 | export class PagedTopics { 4 | public topics: Topic[]; 5 | public continuationToken: string; 6 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/post.ts: -------------------------------------------------------------------------------- 1 | export class Post { 2 | public id: string; 3 | public title: string; 4 | public content: string; 5 | public mediaDescription: string; 6 | public mediaUrl: string; 7 | public mediaType: string; 8 | public userId: string; 9 | public userName: string; 10 | public createdDate: Date; 11 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/product-component.ts: -------------------------------------------------------------------------------- 1 |  2 | import { ProductMedia } from "./product-media"; 3 | 4 | export class ProductComponent { 5 | public componentType: string; 6 | public componentTitle: string; 7 | public componentDetail: string; 8 | public medias: ProductMedia[]; 9 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/product-media.ts: -------------------------------------------------------------------------------- 1 |  2 | export class ProductMedia { 3 | public url: string; 4 | public cdnUrl: string; 5 | public type: string; 6 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/product.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ProductComponent } from "./product-component"; 3 | 4 | export class Product { 5 | public id: string; 6 | public model: string; 7 | public sku: string; 8 | public price: number; 9 | public title: string; 10 | public description: string; 11 | public image: string; 12 | public cdnImage: string; 13 | public rating: number; 14 | public rates: number; 15 | public components: ProductComponent[]; 16 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/register-vm.ts: -------------------------------------------------------------------------------- 1 | export class RegisterVM { 2 | public email: string; 3 | public username: string; 4 | public password: string; 5 | public confirmPassword: string; 6 | public useIdentity: boolean; 7 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/reply.ts: -------------------------------------------------------------------------------- 1 | import { Post } from "./post"; 2 | 3 | export class Reply { 4 | public replyToPostId?: string; 5 | public title: string; 6 | public content: string; 7 | public mediaDescription: string; 8 | public userId: string; 9 | public userName: string; 10 | public mediaFile?: File; 11 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/result-vm.ts: -------------------------------------------------------------------------------- 1 | export class ResultVM { 2 | public result: Result; 3 | public message: string; 4 | public data: any; 5 | } 6 | 7 | export enum Result { 8 | SUCCESS = 1, 9 | Error = 2 10 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/search-item.ts: -------------------------------------------------------------------------------- 1 | export class SearchResult { 2 | public id: string; 3 | public title: string; 4 | public description: string; 5 | public image: string; 6 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/topic.ts: -------------------------------------------------------------------------------- 1 | import { Post } from "./post"; 2 | 3 | export class Topic extends Post { 4 | public posts: Post[]; 5 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/user-cart.ts: -------------------------------------------------------------------------------- 1 | import { Cart } from "./cart"; 2 | import { User } from "./user"; 3 | 4 | export class UserCart { 5 | public cart: Cart; 6 | public user: User; 7 | public useIdentity: boolean; 8 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/models/user.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | public username: string; 3 | public id: string; 4 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/navmenu/navbar.component.css: -------------------------------------------------------------------------------- 1 | .navbar-inverse { 2 | background-color: #31708f; 3 | border-color: transparent; 4 | } 5 | 6 | .navbar-footer { 7 | position: relative; 8 | z-index: 0; 9 | content: ""; 10 | width: 80%; 11 | height: 5px; 12 | -ms-border-radius: 50%; 13 | border-radius: 50%; 14 | -webkit-box-shadow: 0 0 18px #000; 15 | -webkit-box-shadow: 0 0 18px rgba(0,0,0,0.5); 16 | -ms-box-shadow: 0 0 18px #000; 17 | box-shadow: 0 0 18px #000; 18 | box-shadow: 0 0 18px rgba(0,0,0,0.5); 19 | margin: -4px auto 0; 20 | } 21 | 22 | .navbar-search { 23 | display: inline-block; 24 | float: none; 25 | vertical-align: top; 26 | } 27 | @media(max-width:768px){ 28 | .navbar-top { 29 | display: none; 30 | } 31 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/navmenu/navbar.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/navmenu/navbar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { CompleterService, CompleterData, CompleterItem } from 'ng2-completer'; 3 | import { Router } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'nav-bar', 7 | templateUrl: './navbar.component.html', 8 | styleUrls: ['./navbar.component.css'] 9 | }) 10 | 11 | export class NavBarComponent implements OnInit { 12 | @Input() user: any; 13 | 14 | public searchField = '';//bind to the search field text input 15 | suggestions: CompleterData; 16 | searchBy = 'term';//search by paraneter 17 | 18 | constructor(private completerService: CompleterService, 19 | private router: Router) { 20 | this.suggestions = completerService.remote('/api/search/products?term=', 'title', 'title') 21 | .titleField('title'); 22 | } 23 | 24 | public viewProduct(item: CompleterItem) { 25 | if(item && item.originalObject) { 26 | console.log(item.originalObject); 27 | this.router.navigate(['/products/details/' + item.originalObject.id]); 28 | } 29 | } 30 | 31 | ngOnInit() { } 32 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/navmenu/navmenu.component.css: -------------------------------------------------------------------------------- 1 | .navbar-inverse { 2 | background-color: #31708f; 3 | border-color: transparent; 4 | } 5 | 6 | .user-view-nav { 7 | display: none; 8 | } 9 | 10 | li .glyphicon { 11 | margin-right: 10px; 12 | } 13 | 14 | /* Highlighting rules for nav menu items */ 15 | li.link-active a, 16 | li.link-active a:hover, 17 | li.link-active a:focus { 18 | background-color: #4189C7; 19 | color: white; 20 | } 21 | 22 | /* Keep the nav menu independent of scrolling and on top of other items */ 23 | .main-nav { 24 | position: fixed; 25 | top: 51; 26 | left: 0; 27 | right: 0; 28 | z-index: 1; 29 | } 30 | 31 | @media (min-width: 768px) { 32 | /* On small screens, convert the nav menu to a vertical sidebar */ 33 | .main-nav { 34 | height: 100%; 35 | width: calc(25% - 20px); 36 | } 37 | .navbar { 38 | border-radius: 0px; 39 | border-width: 0px; 40 | height: 100%; 41 | } 42 | .navbar-header { 43 | float: none; 44 | } 45 | .navbar-collapse { 46 | border-top: 1px solid #444; 47 | padding: 0px; 48 | } 49 | .navbar ul { 50 | float: none; 51 | } 52 | .navbar li { 53 | float: none; 54 | font-size: 15px; 55 | margin: 6px; 56 | } 57 | .navbar li a { 58 | padding: 10px 16px; 59 | border-radius: 4px; 60 | } 61 | .navbar a { 62 | /* If a menu item's text is too long, truncate it */ 63 | width: 100%; 64 | white-space: nowrap; 65 | overflow: hidden; 66 | text-overflow: ellipsis; 67 | } 68 | 69 | 70 | } 71 | 72 | @media (max-width: 768px) { 73 | /* On small screens, convert the nav menu to a vertical sidebar */ 74 | .main-nav { 75 | top:0; 76 | } 77 | 78 | .user-view-nav { 79 | display: block; 80 | height:277px; 81 | } 82 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/navmenu/navmenu.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/navmenu/navmenu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { User } from '../models/user'; 3 | 4 | @Component({ 5 | selector: 'nav-menu', 6 | templateUrl: './navmenu.component.html', 7 | styleUrls: ['./navmenu.component.css'] 8 | }) 9 | export class NavMenuComponent { 10 | @Input() cart: any; 11 | @Input() user: User; 12 | } 13 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/notifications/notifications.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { RouterModule } from '@angular/router'; 5 | import { StoreModule } from '@ngrx/store'; 6 | import { notificationsReducer } from './store/notifications.reducer'; 7 | import { NotifyService } from '../core/services/notifications.service'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | CommonModule, 12 | StoreModule.forFeature('notifications', { 13 | notificationsState: notificationsReducer, 14 | }) 15 | ], 16 | exports: [], 17 | declarations: [ ], 18 | providers: [ 19 | NotifyService 20 | ], 21 | }) 22 | export class NotificationsModule { } 23 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/notifications/store/notifications.actions.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | import { Message } from '../../models/message'; 3 | 4 | export const SET_LOADING = '[Notify] Set Loading'; 5 | export const SET_MESSAGE = '[Notify] Set Message'; 6 | 7 | export class SetLoadingAction implements Action { 8 | readonly type = SET_LOADING; 9 | 10 | constructor(public loading: boolean) { } 11 | } 12 | 13 | export class SetMessageAction implements Action { 14 | readonly type = SET_MESSAGE; 15 | 16 | constructor(public message: Message) { } 17 | } 18 | 19 | export type Actions 20 | = SetLoadingAction 21 | | SetMessageAction; 22 | 23 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/notifications/store/notifications.reducer.ts: -------------------------------------------------------------------------------- 1 | import { NotificationsState } from './notifications.state'; 2 | import { Action } from '@ngrx/store'; 3 | import * as notifyActions from './notifications.actions'; 4 | import { Result } from '../../models/result-vm'; 5 | 6 | export const initialState: NotificationsState = { 7 | loading: false, 8 | message: undefined 9 | }; 10 | 11 | export function notificationsReducer(state = initialState, action: notifyActions.Actions): NotificationsState { 12 | switch (action.type) { 13 | 14 | case notifyActions.SET_LOADING: 15 | return Object.assign({}, state, { 16 | loading: action.loading 17 | }); 18 | 19 | case notifyActions.SET_MESSAGE: 20 | return Object.assign({}, state, { 21 | message: action.message 22 | }); 23 | 24 | default: 25 | return state; 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/notifications/store/notifications.state.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "../../models/message"; 2 | 3 | export interface NotificationsState { 4 | loading: boolean, 5 | message?: Message 6 | }; -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/orders/orders.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/orders/orders.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Store } from '@ngrx/store'; 3 | //import { ForumState } from '../store/forum.state'; 4 | import { Observable } from 'rxjs/Observable'; 5 | import * as orderActions from '../user/store/user.actions'; 6 | import { Order } from '../models/order'; 7 | import { User } from '../models/user'; 8 | import { ISubscription } from 'rxjs/Subscription'; 9 | //import { Topic } from "../../models/topic"; 10 | 11 | @Component({ 12 | selector: 'orders', 13 | templateUrl: './orders.component.html' 14 | }) 15 | 16 | export class OrdersComponent implements OnInit { 17 | 18 | private subscription: ISubscription; 19 | orders$: Observable; 20 | user$: Observable; 21 | 22 | constructor(private store: Store) { 23 | this.orders$ = this.store.select(state => state.user.userState.orders); 24 | this.user$ = this.store.select(state => state.user.userState.user); 25 | } 26 | 27 | ngOnInit() { 28 | const self = this; 29 | 30 | self.subscription = self.user$ 31 | .filter(user => user !== null && user !== undefined) 32 | .subscribe(user => self.store.dispatch(new orderActions.GetOrdersAction(user.id))); 33 | } 34 | 35 | ngOnDestroy() { 36 | if (this.subscription) 37 | this.subscription.unsubscribe(); 38 | } 39 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/orders/orders.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Order } from '../models/order'; 3 | 4 | @Component({ 5 | selector: 'orders-list-presentation', 6 | templateUrl: './orders.html', 7 | styleUrls: ['./orders.css'], 8 | changeDetection: ChangeDetectionStrategy.OnPush 9 | }) 10 | 11 | export class OrdersPresentationComponent { 12 | 13 | @Input() orders: Order[]; 14 | 15 | constructor() { } 16 | 17 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/details/details.css: -------------------------------------------------------------------------------- 1 |  2 | h2.spec { 3 | color: #4189c7; 4 | } 5 | 6 | .thumbnail img { 7 | width: 50%; 8 | } 9 | 10 | .medias { 11 | margin-top: 15px; 12 | } 13 | 14 | .medias img { 15 | margin-bottom: 15px; 16 | cursor: pointer; 17 | } 18 | 19 | .ratings { 20 | padding-right: 10px; 21 | padding-left: 10px; 22 | color: #d17581; 23 | } 24 | 25 | .product-info { 26 | border: none; 27 | } 28 | 29 | .media-item { 30 | width: 50%; 31 | margin: 10px auto; 32 | } 33 | 34 | .thumbnail { 35 | padding: 0; 36 | } 37 | 38 | .thumbnail .caption-full { 39 | padding: 9px; 40 | color: #333; 41 | } 42 | 43 | @media (max-width:768px) { 44 | .media-item { 45 | width: 10%; 46 | margin: 10px 20px; 47 | display: inline-block; 48 | } 49 | } 50 | 51 | @media (max-width: 480px) { 52 | .thumbnail img { 53 | width: initial; 54 | } 55 | .media-item { 56 | width: 15%; 57 | margin: 5px 10px; 58 | display: inline-block; 59 | } 60 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/details/details.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Product } from "../../models/product"; 3 | import { ProductMedia } from "../../models/product-media"; 4 | import { ProductComponent } from "../../models/product-component"; 5 | 6 | @Component({ 7 | selector: 'product-details-presentation', 8 | templateUrl: './details.html', 9 | styleUrls: ['./details.css'], 10 | changeDetection: ChangeDetectionStrategy.OnPush 11 | }) 12 | 13 | export class ProductDetailsPresentationComponent { 14 | _product: Product; 15 | _medias: ProductMedia[] = []; 16 | _features: ProductComponent[] = []; 17 | _specs: ProductComponent[] = []; 18 | _compatibilites: ProductComponent[] = []; 19 | _imaging: ProductComponent[] = []; 20 | cdnImage: string = ''; 21 | originalImage: string; 22 | 23 | @Input() loading: boolean; 24 | @Input() 25 | set product(value: Product) { 26 | this._product = Object.assign({}, value); 27 | 28 | this.cdnImage = this._product.cdnImage; 29 | this.originalImage = this._product.image; 30 | 31 | if (this._product && this._product.components) { 32 | this._medias = []; 33 | this._features = []; 34 | this._product.components.forEach(component => { 35 | if (component.componentType === 'Media') { 36 | component.medias.forEach(media => { 37 | this._medias.push(media); 38 | } 39 | ); 40 | } 41 | else if (component.componentType === 'Feature') { 42 | this._features.push(component); 43 | } 44 | else if (component.componentType === 'Specification') { 45 | this._specs.push(component); 46 | } 47 | else if (component.componentType === 'Compatibility') { 48 | this._compatibilites.push(component); 49 | } 50 | else if (component.componentType === 'Imaging') { 51 | this._imaging.push(component); 52 | } 53 | }) 54 | } 55 | } 56 | 57 | get product() { 58 | return this._product; 59 | } 60 | 61 | @Output() addToCart: EventEmitter = new EventEmitter(); 62 | 63 | addProduct(productId: string) { 64 | this.addToCart.emit(productId); 65 | } 66 | 67 | setImages(cdnImage: string, originalImage: string) { 68 | this.cdnImage = cdnImage; 69 | this.originalImage = originalImage; 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/details/product-details.html: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/details/product-details.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Store } from '@ngrx/store'; 3 | import { Router, ActivatedRoute, ParamMap, NavigationEnd } from '@angular/router'; 4 | import { Observable } from "rxjs/Observable"; 5 | import * as ProductActions from '../store/product.action'; 6 | import * as userActions from '../../user/store/user.actions'; 7 | import { Product } from "../../models/product"; 8 | import { NotifyService } from '../../core/services/notifications.service'; 9 | 10 | @Component({ 11 | selector: 'product-details', 12 | templateUrl: './product-details.html' 13 | }) 14 | 15 | export class ProductDetailsComponent implements OnInit { 16 | 17 | product$: Observable; 18 | 19 | constructor(private store: Store, private route: ActivatedRoute, 20 | private router: Router, public notifyService: NotifyService) { 21 | this.product$ = this.store.select(state => state.catalog.productState.selectedProduct); 22 | } 23 | 24 | ngOnInit() { 25 | this.route.paramMap 26 | .subscribe((params: ParamMap) => { 27 | this.notifyService.setLoading(true); 28 | this.store.dispatch(new ProductActions.SelectProductAction(params.get('id') || '')) 29 | }); 30 | } 31 | 32 | addProductToCart(productId: string) { 33 | this.store.dispatch(new userActions.AddProductToCartAction(productId)); 34 | } 35 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/list/list.css: -------------------------------------------------------------------------------- 1 | .camera-item { 2 | margin-bottom: 30px; 3 | } 4 | 5 | .camera-item img { 6 | max-height: 115px; 7 | } 8 | 9 | @media (max-width: 768px) { 10 | .camera-item img { 11 | max-height: 130px; 12 | } 13 | } 14 | 15 | @media (max-width: 480px) { 16 | .camera-item { 17 | width: 100%; 18 | } 19 | 20 | .camera-item img { 21 | max-height: initial; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/list/list.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |

6 | Cameras 7 | Point & Shoot Cameras 8 |

9 |
10 |
11 |
12 |
13 | 14 | 15 | 16 |

17 | {{product.title}} 18 |

19 |

20 | {{product.price | currency:'USD':true:'2.2-4'}} 21 | 22 |

23 |
24 |
25 |
-------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/list/list.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Product } from "../../models/product"; 3 | 4 | @Component({ 5 | selector: 'product-list-presentation', 6 | templateUrl: './list.html', 7 | styleUrls: ['./list.css'], 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | }) 10 | 11 | export class ProductListPresentationComponent { 12 | 13 | @Input() products: Product[]; 14 | @Input() loading: boolean; 15 | @Output() addToCart: EventEmitter = new EventEmitter(); 16 | 17 | constructor() { } 18 | 19 | addProduct(productId: string) { 20 | this.addToCart.emit(productId); 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/list/product-list.component.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/list/product-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Store } from '@ngrx/store'; 3 | import { ProductState } from '../store/product.state'; 4 | import { Observable } from 'rxjs/Observable'; 5 | import * as ProductActions from '../store/product.action'; 6 | import * as userActions from '../../user/store/user.actions'; 7 | import { Product } from "../../models/product"; 8 | import { NotifyService } from '../../core/services/notifications.service'; 9 | 10 | @Component({ 11 | selector: 'product-list', 12 | templateUrl: './product-list.component.html', 13 | 14 | }) 15 | 16 | export class ProductListComponent implements OnInit { 17 | 18 | products$: Observable; 19 | 20 | constructor(private store: Store, public notifyService: NotifyService) { 21 | this.products$ = this.store.select(state => state.catalog.productState.products); 22 | } 23 | 24 | ngOnInit() { 25 | this.notifyService.setLoading(true); 26 | this.store.dispatch(new ProductActions.SelectAllAction()); 27 | } 28 | 29 | addProductToCart(productId: string) { 30 | this.store.dispatch(new userActions.AddProductToCartAction(productId)); 31 | } 32 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/product.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Store } from '@ngrx/store'; 3 | import { Observable } from 'rxjs/Observable'; 4 | 5 | @Component({ 6 | selector: 'product-view', 7 | template: '' 8 | }) 9 | 10 | export class ProductComponent implements OnInit { 11 | 12 | 13 | constructor() { } 14 | 15 | ngOnInit() { } 16 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/product.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ProductListComponent } from './list/product-list.component'; 4 | import { ProductDetailsComponent } from './details/product-details'; 5 | import { PRODUCT_ROUTES } from './product.routes'; 6 | import { StoreModule } from '@ngrx/store'; 7 | import { EffectsModule } from '@ngrx/effects'; 8 | import { productReducer } from './store/product.reducer'; 9 | import { ProductEffects } from './store/product.effects'; 10 | import { FormsModule } from '@angular/forms'; 11 | import { ProductComponent } from "./product.component"; 12 | import { ProductListPresentationComponent } from './list/list'; 13 | import { ProductDetailsPresentationComponent } from "./details/details"; 14 | 15 | import { ImgFallbackModule } from 'ngx-img-fallback'; 16 | 17 | const PRODUCT_DIRECTIVES = [ 18 | ProductListComponent, 19 | ProductListPresentationComponent, 20 | ProductDetailsComponent, 21 | ProductDetailsPresentationComponent, 22 | ProductComponent 23 | ]; 24 | 25 | @NgModule({ 26 | imports: [ 27 | CommonModule, 28 | FormsModule, 29 | PRODUCT_ROUTES, 30 | ImgFallbackModule, 31 | StoreModule.forFeature('catalog', { 32 | productState: productReducer, 33 | }), 34 | EffectsModule.forFeature([ProductEffects]) 35 | ], 36 | exports: [ 37 | ...PRODUCT_DIRECTIVES 38 | ], 39 | declarations: [ 40 | ...PRODUCT_DIRECTIVES 41 | ], 42 | providers: [], 43 | }) 44 | export class ProductModule { } 45 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/product.routes.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | 3 | import { ProductDetailsComponent } from './details/product-details'; 4 | import { ProductListComponent } from './list/product-list.component'; 5 | import { ProductComponent } from "./product.component"; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', component: ProductComponent, children: [ 10 | { path: '', redirectTo: 'list', pathMatch: 'full' }, 11 | { path: 'list', component: ProductListComponent }, 12 | { path: 'details/:id', component: ProductDetailsComponent } 13 | ] 14 | } 15 | ]; 16 | 17 | export const PRODUCT_ROUTES = RouterModule.forChild(routes); 18 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/store/product.action.ts: -------------------------------------------------------------------------------- 1 | import { Action } from '@ngrx/store'; 2 | import { Product } from './../../models/product'; 3 | import { Cart } from "../../models/cart"; 4 | 5 | export const SELECTALL = '[Product] Select All'; 6 | export const SELECTALL_COMPLETE = '[Product] Select All Complete'; 7 | export const SELECT_PRODUCT = '[Product] Select Product'; 8 | export const SELECT_PRODUCT_COMPLETE = '[Product] Select Product Complete'; 9 | 10 | 11 | export class SelectAllAction implements Action { 12 | readonly type = SELECTALL; 13 | 14 | constructor() { } 15 | } 16 | 17 | export class SelectAllCompleteAction implements Action { 18 | readonly type = SELECTALL_COMPLETE; 19 | 20 | constructor(public products: Product[]) { } 21 | } 22 | 23 | export class SelectProductAction implements Action { 24 | readonly type = SELECT_PRODUCT; 25 | 26 | constructor(public id: string) { } 27 | } 28 | 29 | export class SelectProductCompleteAction implements Action { 30 | readonly type = SELECT_PRODUCT_COMPLETE; 31 | 32 | constructor(public product: Product) { } 33 | } 34 | 35 | 36 | 37 | export type Actions 38 | = SelectAllAction 39 | | SelectAllCompleteAction 40 | | SelectProductAction 41 | | SelectProductCompleteAction; 42 | 43 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/store/product.effects.ts: -------------------------------------------------------------------------------- 1 | import 'rxjs/add/operator/map'; 2 | import 'rxjs/add/operator/switchMap'; 3 | 4 | import { Injectable } from '@angular/core'; 5 | import { Actions, Effect } from '@ngrx/effects'; 6 | import { Action } from '@ngrx/store'; 7 | import { of } from 'rxjs/observable/of'; 8 | import { Observable } from 'rxjs/Rx'; 9 | 10 | import * as productsAction from './product.action'; 11 | import * as notifyActions from '../../notifications/store/notifications.actions'; 12 | import { Product } from './../../models/product'; 13 | import { ProductService } from '../../core/services/product.service'; 14 | import { Cart } from "../../models/cart"; 15 | 16 | @Injectable() 17 | export class ProductEffects { 18 | 19 | @Effect() getAll$: Observable = this.actions$.ofType(productsAction.SELECTALL) 20 | .switchMap(() => 21 | this.productService.getAll() 22 | .mergeMap((data: Product[]) => { 23 | return [ 24 | new notifyActions.SetLoadingAction(false), 25 | new productsAction.SelectAllCompleteAction(data) 26 | ]; 27 | }) 28 | .catch((error: any) => { 29 | return of({ type: 'getAll_FAILED' }) 30 | }) 31 | ); 32 | 33 | @Effect() getProduct$: Observable = this.actions$.ofType(productsAction.SELECT_PRODUCT) 34 | .switchMap((action: productsAction.SelectProductAction) => { 35 | return this.productService.getSingle(action.id) 36 | .mergeMap((data: Product) => { 37 | return [ 38 | new notifyActions.SetLoadingAction(false), 39 | new productsAction.SelectProductCompleteAction(data) 40 | ]; 41 | }) 42 | .catch((error: any) => { 43 | return of({ type: 'getProduct_FAILED' }) 44 | }) 45 | } 46 | ); 47 | 48 | constructor( 49 | private productService: ProductService, 50 | private actions$: Actions 51 | ) { } 52 | } 53 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/store/product.reducer.ts: -------------------------------------------------------------------------------- 1 | import { ProductState } from './product.state'; 2 | import { Product } from './../../models/product'; 3 | import { Action } from '@ngrx/store'; 4 | import * as productsAction from './product.action'; 5 | 6 | export const initialState: ProductState = { 7 | products: [], 8 | selectedProduct: undefined 9 | }; 10 | 11 | export function productReducer(state = initialState, action: productsAction.Actions): ProductState { 12 | switch (action.type) { 13 | 14 | case productsAction.SELECTALL_COMPLETE: 15 | return Object.assign({}, state, { 16 | products: action.products 17 | }); 18 | 19 | case productsAction.SELECT_PRODUCT_COMPLETE: 20 | return Object.assign({}, state, { 21 | selectedProduct: action.product 22 | }); 23 | 24 | default: 25 | return state; 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/product/store/product.state.ts: -------------------------------------------------------------------------------- 1 | import { Product } from './../../models/product'; 2 | import { Cart } from "../../models/cart"; 3 | 4 | // product state 5 | export interface ProductState { 6 | products: Product[], 7 | selectedProduct?: Product, 8 | }; -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { ActionReducerMap } from '@ngrx/store'; 2 | 3 | import { userReducer } from '../user/store/user.reducer'; 4 | import { UserState } from '../user/store/user.state'; 5 | 6 | /** 7 | * As mentioned, we treat each reducer like a table in a database. This means 8 | * our top level state interface is just a map of keys to inner state types. 9 | */ 10 | export interface State { 11 | user: UserState; 12 | } 13 | 14 | /** 15 | * Our state is composed of a map of action reducer functions. 16 | * These reducer functions are called with each dispatched action 17 | * and the current or initial state and return a new immutable state. 18 | */ 19 | export const reducers: ActionReducerMap = { 20 | user: userReducer, 21 | }; -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/user/register/register.component.html: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/user/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Store } from '@ngrx/store'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import * as userActions from '../../user/store/user.actions'; 5 | import { Topic } from "../../models/topic"; 6 | import { RegisterVM } from '../../models/register-vm'; 7 | import { ISubscription } from "rxjs/Subscription"; 8 | import { LoginVM } from '../../models/login-vm'; 9 | 10 | @Component({ 11 | selector: 'user-register', 12 | templateUrl: './register.component.html', 13 | 14 | }) 15 | 16 | export class UserRegisterComponent implements OnInit { 17 | 18 | newUser$: Observable; 19 | useIdentity$: Observable; 20 | private subscription: ISubscription; 21 | 22 | constructor(private store: Store) { 23 | this.useIdentity$ = this.store.select(state => state.user.userState.useIdentity); 24 | } 25 | 26 | ngOnInit() { 27 | } 28 | 29 | registerUser(user: RegisterVM) { 30 | this.useIdentity$.take(1).subscribe(useidentity => { 31 | this.store.dispatch(new userActions.RegisterUserAction(user)); 32 | }) 33 | } 34 | 35 | loginUser(user: LoginVM) { 36 | this.store.dispatch(new userActions.LoginUserAction(user)); 37 | } 38 | 39 | ngOnDestroy() { 40 | //this.subscription.unsubscribe(); 41 | } 42 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/user/register/register.css: -------------------------------------------------------------------------------- 1 | #register-dp{ 2 | min-width: 250px; 3 | padding: 14px 14px 0; 4 | overflow:hidden; 5 | background-color:rgba(255,255,255,.8); 6 | } 7 | #register-dp .help-block{ 8 | font-size:12px 9 | } 10 | #register-dp .bottom{ 11 | background-color:rgba(255,255,255,.8); 12 | border-top:1px solid #ddd; 13 | clear:both; 14 | padding:14px; 15 | } 16 | #register-dp .social-buttons{ 17 | margin:12px 0 18 | } 19 | #register-dp .social-buttons a{ 20 | width: 49%; 21 | } 22 | #register-dp .form-group { 23 | margin-bottom: 10px; 24 | } 25 | .btn-fb{ 26 | color: #fff; 27 | background-color:#3b5998; 28 | } 29 | .btn-fb:hover{ 30 | color: #fff; 31 | background-color:#496ebc 32 | } 33 | .btn-tw{ 34 | color: #fff; 35 | background-color:#55acee; 36 | } 37 | .btn-tw:hover{ 38 | color: #fff; 39 | background-color:#59b5fa; 40 | } 41 | @media(max-width:768px){ 42 | #register-dp{ 43 | background-color: inherit; 44 | color: #fff; 45 | } 46 | #register-dp .bottom{ 47 | background-color: inherit; 48 | border-top:0 none; 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/user/register/register.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; 2 | import { RegisterVM } from '../../models/register-vm'; 3 | import { LoginVM } from '../../models/login-vm'; 4 | 5 | @Component({ 6 | selector: 'user-register-presentation', 7 | templateUrl: './register.html', 8 | styleUrls: ['./register.css'], 9 | changeDetection: ChangeDetectionStrategy.OnPush 10 | }) 11 | 12 | export class UserRegisterPresentationComponent { 13 | 14 | user: RegisterVM = { username : '', email: '', password: '', confirmPassword: '', useIdentity: false }; 15 | login: LoginVM = { username: '', password: '', rememberMe: true } 16 | 17 | @Input() useIdentity: boolean; 18 | @Output() onRegister: EventEmitter = new EventEmitter(); 19 | @Output() onLogin: EventEmitter = new EventEmitter(); 20 | 21 | constructor() { } 22 | 23 | register() { 24 | this.user.useIdentity = this.useIdentity; 25 | this.onRegister.emit(this.user); 26 | } 27 | 28 | signIn() { 29 | this.onLogin.emit(this.login); 30 | } 31 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/user/store/user.reducer.ts: -------------------------------------------------------------------------------- 1 | import { UserState } from './user.state'; 2 | import { Action } from '@ngrx/store'; 3 | import * as userActions from './user.actions'; 4 | import { Result } from '../../models/result-vm'; 5 | import { CartItem } from '../../models/cart-item'; 6 | 7 | export const initialState: UserState = { 8 | cart: undefined, 9 | cartTotal: 0, 10 | user: undefined, 11 | redirectToLogin: false, 12 | orders: [], 13 | useIdentity: false 14 | }; 15 | 16 | export function userReducer(state = initialState, action: userActions.Actions): UserState { 17 | switch (action.type) { 18 | 19 | case userActions.GET_CART_COMPLETE: 20 | return Object.assign({}, state, { 21 | cart: action.userCart !== null ? action.userCart.cart : undefined, 22 | cartTotal: calculateTotal(action.userCart.cart), 23 | user: action.userCart !== null ? action.userCart.user : undefined, 24 | useIdentity: action.userCart.useIdentity 25 | }); 26 | 27 | case userActions.ADD_PRODUCT_TO_CART_COMPLETE: 28 | case userActions.REMOVE_PRODUCT_FROM_CART_COMPLETE: 29 | return Object.assign({}, state, { 30 | cart: action.cart, 31 | cartTotal: calculateTotal(action.cart) 32 | }); 33 | 34 | case userActions.LOGIN_USER_COMPLETE: 35 | return Object.assign({}, state, { 36 | user: action.result.data 37 | }); 38 | 39 | case userActions.REGISTER_USER_COMPLETE: 40 | return Object.assign({}, state, { 41 | redirectToLogin: (action.result.result === Result.SUCCESS && action.result.data.redirect) ? true : false, 42 | user: action.result.data 43 | }); 44 | 45 | case userActions.GET_ORDERS_COMPLETE: 46 | return Object.assign({}, state, { 47 | orders: action.orders 48 | }); 49 | 50 | case userActions.COMPLETE_ORDER_COMPLETE: 51 | return Object.assign({}, state, { 52 | cart: undefined, 53 | cartTotal: 0 54 | }); 55 | 56 | default: 57 | return state; 58 | 59 | } 60 | } 61 | 62 | function calculateTotal(cart: any) { 63 | let total = 0; 64 | 65 | if (cart !== null && cart !== undefined) { 66 | if (cart.items !== undefined && cart.items.length > 0) { 67 | cart.items.forEach((item: CartItem) => { 68 | total += item.price * item.quantity; 69 | }); 70 | } 71 | } 72 | 73 | return total; 74 | } 75 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/user/store/user.state.ts: -------------------------------------------------------------------------------- 1 | import { Cart } from "../../models/cart"; 2 | import { User } from "../../models/user"; 3 | import { Order } from "../../models/order"; 4 | 5 | export interface UserState { 6 | cart?: Cart, 7 | cartTotal: number, 8 | user?: User, 9 | redirectToLogin: boolean, 10 | orders: Order[], 11 | useIdentity: boolean 12 | }; -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/user/user.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/user/user.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Store } from '@ngrx/store'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { User } from '../models/user'; 5 | import { ISubscription } from 'rxjs/Subscription'; 6 | 7 | @Component({ 8 | selector: 'user-view', 9 | templateUrl: './user.component.html' 10 | }) 11 | 12 | export class UserComponent implements OnInit { 13 | 14 | redirectToLogin$: Observable; 15 | user$: Observable; 16 | useIdentity$: Observable; 17 | 18 | private subscription: ISubscription; 19 | 20 | constructor(private store: Store) { 21 | this.redirectToLogin$ = this.store.select(state => state.user.userState.redirectToLogin); 22 | this.user$ = this.store.select(state => state.user.userState.user); 23 | this.useIdentity$ = this.store.select(state => state.user.userState.useIdentity); 24 | } 25 | 26 | ngOnInit() { 27 | this.subscription = this.redirectToLogin$ 28 | .filter(val => val === true) 29 | .subscribe(() => 30 | setTimeout(function () { 31 | window.location.href = '/account/signin'; 32 | }, 1500)); 33 | } 34 | 35 | ngOnDestroy() { 36 | this.subscription.unsubscribe(); 37 | } 38 | } -------------------------------------------------------------------------------- /Online.Store/ClientApp/app/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { RouterModule } from '@angular/router'; 5 | import { StoreModule } from '@ngrx/store'; 6 | import { EffectsModule } from '@ngrx/effects'; 7 | import { userReducer } from './store/user.reducer'; 8 | import { UserEffects } from './store/user.effects'; 9 | import { UserRegisterPresentationComponent } from './register/register'; 10 | import { UserRegisterComponent } from './register/register.component'; 11 | import { UserComponent } from './user.component'; 12 | 13 | const USER_DIRECTIVES = [ 14 | UserComponent, 15 | UserRegisterComponent, 16 | UserRegisterPresentationComponent 17 | ]; 18 | 19 | @NgModule({ 20 | imports: [ 21 | CommonModule, 22 | FormsModule, 23 | RouterModule, 24 | StoreModule.forFeature('user', { 25 | userState: userReducer, 26 | }), 27 | EffectsModule.forFeature([UserEffects]) 28 | ], 29 | exports: [ 30 | ...USER_DIRECTIVES 31 | ], 32 | declarations: [ 33 | ...USER_DIRECTIVES 34 | ], 35 | providers: [], 36 | }) 37 | export class UserModule { } 38 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/boot.browser.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import 'zone.js'; 3 | import 'bootstrap'; 4 | import { enableProdMode } from '@angular/core'; 5 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 6 | import { AppModule } from './app/app.module.browser'; 7 | 8 | if (module.hot) { 9 | module.hot.accept(); 10 | module.hot.dispose(() => { 11 | // Before restarting the app, we create a new root element and dispose the old one 12 | const oldRootElem = document.querySelector('app'); 13 | const newRootElem = document.createElement('app'); 14 | oldRootElem!.parentNode!.insertBefore(newRootElem, oldRootElem); 15 | modulePromise.then(appModule => appModule.destroy()); 16 | }); 17 | } else { 18 | enableProdMode(); 19 | } 20 | 21 | // Note: @ng-tools/webpack looks for the following expression when performing production 22 | // builds. Don't change how this line looks, otherwise you may break tree-shaking. 23 | const modulePromise = platformBrowserDynamic().bootstrapModule(AppModule); 24 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/boot.server.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import 'zone.js'; 3 | import 'rxjs/add/operator/first'; 4 | import { APP_BASE_HREF } from '@angular/common'; 5 | import { enableProdMode, ApplicationRef, NgZone, ValueProvider } from '@angular/core'; 6 | import { platformDynamicServer, PlatformState, INITIAL_CONFIG } from '@angular/platform-server'; 7 | import { createServerRenderer, RenderResult } from 'aspnet-prerendering'; 8 | import { AppModule } from './app/app.module.server'; 9 | 10 | enableProdMode(); 11 | 12 | export default createServerRenderer(params => { 13 | const providers = [ 14 | { provide: INITIAL_CONFIG, useValue: { document: '', url: params.url } }, 15 | { provide: APP_BASE_HREF, useValue: params.baseUrl }, 16 | { provide: 'BASE_URL', useValue: params.origin + params.baseUrl }, 17 | ]; 18 | 19 | return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => { 20 | const appRef: ApplicationRef = moduleRef.injector.get(ApplicationRef); 21 | const state = moduleRef.injector.get(PlatformState); 22 | const zone = moduleRef.injector.get(NgZone); 23 | 24 | return new Promise((resolve, reject) => { 25 | zone.onError.subscribe((errorInfo: any) => reject(errorInfo)); 26 | appRef.isStable.first(isStable => isStable).subscribe(() => { 27 | // Because 'onStable' fires before 'onError', we have to delay slightly before 28 | // completing the request in case there's an error to report 29 | setImmediate(() => { 30 | resolve({ 31 | html: state.renderToString() 32 | }); 33 | moduleRef.destroy(); 34 | }); 35 | }); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/test/boot-tests.ts: -------------------------------------------------------------------------------- 1 | // Load required polyfills and testing libraries 2 | import 'reflect-metadata'; 3 | import 'zone.js'; 4 | import 'zone.js/dist/long-stack-trace-zone'; 5 | import 'zone.js/dist/proxy.js'; 6 | import 'zone.js/dist/sync-test'; 7 | import 'zone.js/dist/jasmine-patch'; 8 | import 'zone.js/dist/async-test'; 9 | import 'zone.js/dist/fake-async-test'; 10 | import * as testing from '@angular/core/testing'; 11 | import * as testingBrowser from '@angular/platform-browser-dynamic/testing'; 12 | 13 | // There's no typing for the `__karma__` variable. Just declare it as any 14 | declare var __karma__: any; 15 | declare var require: any; 16 | 17 | // Prevent Karma from running prematurely 18 | __karma__.loaded = function () {}; 19 | 20 | // First, initialize the Angular testing environment 21 | testing.getTestBed().initTestEnvironment( 22 | testingBrowser.BrowserDynamicTestingModule, 23 | testingBrowser.platformBrowserDynamicTesting() 24 | ); 25 | 26 | // Then we find all the tests 27 | const context = require.context('../', true, /\.spec\.ts$/); 28 | 29 | // And load the modules 30 | context.keys().map(context); 31 | 32 | // Finally, start Karma to run the tests 33 | __karma__.start(); 34 | -------------------------------------------------------------------------------- /Online.Store/ClientApp/test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '.', 7 | frameworks: ['jasmine'], 8 | files: [ 9 | '../../wwwroot/dist/vendor.js', 10 | './boot-tests.ts' 11 | ], 12 | preprocessors: { 13 | './boot-tests.ts': ['webpack'] 14 | }, 15 | reporters: ['progress'], 16 | port: 9876, 17 | colors: true, 18 | logLevel: config.LOG_INFO, 19 | autoWatch: true, 20 | browsers: ['Chrome'], 21 | mime: { 'application/javascript': ['ts','tsx'] }, 22 | singleRun: false, 23 | webpack: require('../../webpack.config.js')().filter(config => config.target !== 'node'), // Test against client bundle, because tests run in a browser 24 | webpackMiddleware: { stats: 'errors-only' } 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /Online.Store/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Extensions.Configuration; 9 | using Online.Store.Models; 10 | 11 | namespace Online.Store.Controllers 12 | { 13 | public class HomeController : Controller 14 | { 15 | IConfiguration _configuration; 16 | 17 | public HomeController(IConfiguration configuration) 18 | { 19 | _configuration = configuration; 20 | } 21 | public IActionResult Index() 22 | { 23 | ViewData.Add("Region", _configuration["Region"]); 24 | return View(); 25 | } 26 | 27 | public IActionResult Error() 28 | { 29 | ViewData["RequestId"] = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 30 | return View(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Online.Store/Controllers/SearchController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Online.Store.Azure.Services; 8 | using Microsoft.Extensions.Configuration; 9 | using Online.Store.ViewModels; 10 | using AutoMapper; 11 | using Online.Store.Core.DTOs; 12 | 13 | namespace Online.Store.Controllers 14 | { 15 | [Produces("application/json")] 16 | [Route("api/Search")] 17 | public class SearchController : Controller 18 | { 19 | private IAzureSearchService _searchService; 20 | 21 | public SearchController(IAzureSearchService searchService) 22 | { 23 | _searchService = searchService; 24 | } 25 | 26 | // GET: api/Search/term 27 | [HttpGet("products/", Name = "Search")] 28 | public async Task> Get(string term) 29 | { 30 | var products = new List(); 31 | var productsSearch = await _searchService.SearchProductsAsync(term); 32 | 33 | products = Mapper.Map, List>(productsSearch); 34 | 35 | return products; 36 | } 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Online.Store/Core/AdUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Online.Store.Core 7 | { 8 | public class ActiveDirectoryUser 9 | { 10 | public string ObjectId { get; set; } 11 | public bool AccountEnabled { get; set; } 12 | public string CreationType { get; set; } 13 | public string UserPrincipalName { get; set; } 14 | public List SignInNames { get; set; } 15 | } 16 | 17 | public class SignInName 18 | { 19 | public string Type { get; set; } 20 | public string Value { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Online.Store/Core/ModelStateExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ModelBinding; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Core 8 | { 9 | public static class ModelStateExtensions 10 | { 11 | public static IEnumerable AllErrors(this ModelStateDictionary modelState) 12 | { 13 | var result = new List(); 14 | var erroneousFields = modelState.Where(ms => ms.Value.Errors.Any()) 15 | .Select(x => new { x.Key, x.Value.Errors }); 16 | 17 | foreach (var erroneousField in erroneousFields) 18 | { 19 | var fieldKey = erroneousField.Key; 20 | var fieldErrors = erroneousField.Errors 21 | .Select(error => new Error(fieldKey, error.ErrorMessage)); 22 | result.AddRange(fieldErrors); 23 | } 24 | 25 | return result; 26 | } 27 | 28 | public static string ListMessages(this ModelStateDictionary modelState) 29 | { 30 | string message = "
    "; 31 | 32 | var errors = modelState.AllErrors(); 33 | 34 | foreach(var error in errors) 35 | { 36 | message+= "
  • " + error.Message + "
  • "; 37 | } 38 | 39 | message += "
"; 40 | 41 | return message; 42 | } 43 | } 44 | 45 | public class Error 46 | { 47 | public Error(string key, string message) 48 | { 49 | Key = key; 50 | Message = message; 51 | } 52 | 53 | public string Key { get; set; } 54 | public string Message { get; set; } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Online.Store/Data/ApplicationDbContext.cs: -------------------------------------------------------------------------------- 1 | /* 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 7 | using Microsoft.EntityFrameworkCore; 8 | using Online.Store.Models; 9 | using Microsoft.EntityFrameworkCore.Design; 10 | using Microsoft.Extensions.Configuration; 11 | using Online.Store.Core.DTOs; 12 | using Microsoft.EntityFrameworkCore.Metadata.Internal; 13 | 14 | namespace Online.Store.Data 15 | { 16 | public class ApplicationDbContext : DbContext 17 | { 18 | public DbSet Orders { get; set; } 19 | 20 | public ApplicationDbContext(DbContextOptions options) 21 | : base(options) 22 | { 23 | } 24 | 25 | protected override void OnModelCreating(ModelBuilder builder) 26 | { 27 | base.OnModelCreating(builder); 28 | 29 | foreach (var entity in builder.Model.GetEntityTypes()) 30 | { 31 | entity.Relational().TableName = entity.DisplayName(); 32 | } 33 | } 34 | } 35 | 36 | public class TemporaryDbContextFactory : IDesignTimeDbContextFactory 37 | { 38 | //////// 39 | public ApplicationDbContext CreateDbContext(string[] args) 40 | { 41 | var builder = new DbContextOptionsBuilder(); 42 | IConfigurationRoot configuration = new ConfigurationBuilder() 43 | .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) 44 | .AddJsonFile("appsettings.json") 45 | .Build(); 46 | 47 | builder.UseSqlServer(configuration.GetConnectionString("DefaultConnection")); 48 | return new ApplicationDbContext(builder.Options); 49 | } 50 | 51 | 52 | } 53 | } 54 | */ -------------------------------------------------------------------------------- /Online.Store/Extensions/AntiforgeryTokenMiddlewareExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Antiforgery; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Http; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace Online.Store.Extensions 10 | { 11 | public class AntiforgeryTokenMiddleware 12 | { 13 | private readonly RequestDelegate _next; 14 | private readonly IAntiforgery _antiforgery; 15 | 16 | public AntiforgeryTokenMiddleware(RequestDelegate next, IAntiforgery antiforgery) 17 | { 18 | _next = next; 19 | _antiforgery = antiforgery; 20 | } 21 | 22 | public Task Invoke(HttpContext context) 23 | { 24 | if (context.Request.Path == "/") 25 | { 26 | var tokens = _antiforgery.GetAndStoreTokens(context); 27 | context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions { HttpOnly = false }); 28 | } 29 | return _next(context); 30 | } 31 | } 32 | 33 | public static class AntiforgeryTokenMiddlewareExtensions 34 | { 35 | public static IApplicationBuilder UseAntiforgeryToken(this IApplicationBuilder builder) 36 | { 37 | return builder.UseMiddleware(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Online.Store/Extensions/AzureAdAuthenticationBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Authentication.OpenIdConnect; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Options; 6 | 7 | namespace Microsoft.AspNetCore.Authentication 8 | { 9 | public static class AzureAdAuthenticationBuilderExtensions 10 | { 11 | public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder) 12 | => builder.AddAzureAd(_ => { }); 13 | 14 | public static AuthenticationBuilder AddAzureAd(this AuthenticationBuilder builder, Action configureOptions) 15 | { 16 | builder.Services.Configure(configureOptions); 17 | builder.Services.AddSingleton, ConfigureAzureOptions>(); 18 | builder.AddOpenIdConnect(); 19 | return builder; 20 | } 21 | 22 | private class ConfigureAzureOptions: IConfigureNamedOptions 23 | { 24 | private readonly AzureAdOptions _azureOptions; 25 | 26 | public ConfigureAzureOptions(IOptions azureOptions) 27 | { 28 | _azureOptions = azureOptions.Value; 29 | } 30 | 31 | public void Configure(string name, OpenIdConnectOptions options) 32 | { 33 | options.ClientId = _azureOptions.ClientId; 34 | options.Authority = $"{_azureOptions.Instance}{_azureOptions.TenantId}"; 35 | options.UseTokenLifetime = true; 36 | options.CallbackPath = _azureOptions.CallbackPath; 37 | options.RequireHttpsMetadata = false; 38 | } 39 | 40 | public void Configure(OpenIdConnectOptions options) 41 | { 42 | Configure(Options.DefaultName, options); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Online.Store/Extensions/AzureAdOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.AspNetCore.Authentication 2 | { 3 | public class AzureAdOptions 4 | { 5 | public string ClientId { get; set; } 6 | 7 | public string ClientSecret { get; set; } 8 | 9 | public string Instance { get; set; } 10 | 11 | public string Domain { get; set; } 12 | 13 | public string TenantId { get; set; } 14 | 15 | public string CallbackPath { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Online.Store/Extensions/EmailSenderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.Encodings.Web; 5 | using System.Threading.Tasks; 6 | using Online.Store.Services; 7 | 8 | namespace Online.Store.Services 9 | { 10 | public static class EmailSenderExtensions 11 | { 12 | public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link) 13 | { 14 | return emailSender.SendEmailAsync(email, "Confirm your email", 15 | $"Please confirm your account by clicking this link: link"); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Online.Store/Extensions/UrlHelperExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Online.Store.Controllers; 6 | 7 | namespace Microsoft.AspNetCore.Mvc 8 | { 9 | public static class UrlHelperExtensions 10 | { 11 | public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme) 12 | { 13 | return urlHelper.Action( 14 | action: nameof(IdentityController.ConfirmEmail), 15 | controller: "Account", 16 | values: new { userId, code }, 17 | protocol: scheme); 18 | } 19 | 20 | public static string ResetPasswordCallbackLink(this IUrlHelper urlHelper, string userId, string code, string scheme) 21 | { 22 | return urlHelper.Action( 23 | action: nameof(IdentityController.ResetPassword), 24 | controller: "Account", 25 | values: new { userId, code }, 26 | protocol: scheme); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Online.Store/Identity/ApplicationIdentityDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Design; 4 | using Microsoft.Extensions.Configuration; 5 | using Online.Store.Models; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Threading.Tasks; 10 | using Microsoft.EntityFrameworkCore.Metadata.Internal; 11 | 12 | namespace Online.Store.Identity 13 | { 14 | public class ApplicationIdentityDbContext : IdentityDbContext 15 | { 16 | public ApplicationIdentityDbContext(DbContextOptions options) 17 | : base(options) 18 | { 19 | } 20 | 21 | protected override void OnModelCreating(ModelBuilder builder) 22 | { 23 | base.OnModelCreating(builder); 24 | 25 | foreach (var entity in builder.Model.GetEntityTypes()) 26 | { 27 | entity.Relational().TableName = entity.DisplayName(); 28 | } 29 | } 30 | } 31 | 32 | public class TemporaryDbContextFactory : IDesignTimeDbContextFactory 33 | { 34 | //////// 35 | public ApplicationIdentityDbContext CreateDbContext(string[] args) 36 | { 37 | var builder = new DbContextOptionsBuilder(); 38 | IConfigurationRoot configuration = new ConfigurationBuilder() 39 | .SetBasePath(AppDomain.CurrentDomain.BaseDirectory) 40 | .AddJsonFile("appsettings.json") 41 | .Build(); 42 | 43 | builder.UseSqlServer(configuration.GetConnectionString("IdentityConnection")); 44 | return new ApplicationIdentityDbContext(builder.Options); 45 | } 46 | 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Online.Store/Mappings/AutoMapperConfiguration.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using Microsoft.Extensions.Configuration; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Online.Store.Mappings 9 | { 10 | public class AutoMapperConfiguration 11 | { 12 | public static void Configure(IConfiguration configuration) 13 | { 14 | Mapper.Initialize(x => 15 | { 16 | x.AddProfile(new DomainToViewModelProfile(configuration)); 17 | }); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Online.Store/Mappings/DomainToViewModelProfile.cs: -------------------------------------------------------------------------------- 1 | using Online.Store.Core.DTOs; 2 | using Online.Store.ViewModels; 3 | using AutoMapper; 4 | using Microsoft.Extensions.Configuration; 5 | 6 | namespace Online.Store.Mappings 7 | { 8 | public class DomainToViewModelProfile : Profile 9 | { 10 | public DomainToViewModelProfile(IConfiguration configuration) 11 | { 12 | bool isDevStorageAccount = configuration["Storage:AccountName"] == "devstoreaccount1"; 13 | string storageBlobEndpoint = string.Format("https://{0}.blob.core.windows.net/", configuration["Storage:AccountName"]); 14 | string cdnEndpoint = configuration["CDN:Endpoint"]; 15 | 16 | CreateMap() 17 | .ForMember(vm => vm.Components, map => map.MapFrom(p => p.Components)) 18 | .ForMember(vm => vm.Image, map => map.MapFrom(p => isDevStorageAccount ? 19 | p.Image.Replace(storageBlobEndpoint, "http://127.0.0.1:10000/devstoreaccount1/") : p.Image)) 20 | .ForMember(vm => vm.CdnImage, map => map.MapFrom(i => 21 | i.Image.Replace(i.Image.Substring(0, i.Image.LastIndexOf(".net") + 4), cdnEndpoint))); 22 | 23 | CreateMap() 24 | .ForMember(vm => vm.Medias, map => map.MapFrom(c => c.Medias)); 25 | 26 | CreateMap() 27 | .ForMember(vm => vm.Url, map => map.MapFrom(m => 28 | isDevStorageAccount ? m.Url.Replace(storageBlobEndpoint, "http://127.0.0.1:10000/devstoreaccount1/") : m.Url)) 29 | .ForMember(vm => vm.CdnUrl, map => map.MapFrom(i => 30 | i.Url.Replace(i.Url.Substring(0, i.Url.LastIndexOf(".net") + 4), cdnEndpoint))); 31 | 32 | CreateMap(); 33 | CreateMap() 34 | .ForMember(vm => vm.ProductImage, map => map.MapFrom(od => 35 | storageBlobEndpoint + "product-images/" + od.ProductModel + "/1.jpg")) 36 | .ForMember(vm => vm.ProductCdnImage, map => map.MapFrom(od => 37 | cdnEndpoint + "/product-images/" + od.ProductModel + "/1.jpg")); 38 | 39 | CreateMap() 40 | .ForMember(vm => vm.CdnImage, map => map.MapFrom(i => 41 | i.Image.Replace(i.Image.Substring(0, i.Image.LastIndexOf(".net") + 4), cdnEndpoint))); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Online.Store/Models/AccountViewModels/ExternalLoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Models.AccountViewModels 8 | { 9 | public class ExternalLoginViewModel 10 | { 11 | [Required] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Online.Store/Models/AccountViewModels/ForgotPasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Models.AccountViewModels 8 | { 9 | public class ForgotPasswordViewModel 10 | { 11 | [Required] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Online.Store/Models/AccountViewModels/LoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Models.AccountViewModels 8 | { 9 | public class LoginViewModel 10 | { 11 | [Required] 12 | public string Username { get; set; } 13 | 14 | [Required] 15 | [DataType(DataType.Password)] 16 | public string Password { get; set; } 17 | 18 | [Display(Name = "Remember me?")] 19 | public bool RememberMe { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Online.Store/Models/AccountViewModels/LoginWith2faViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Models.AccountViewModels 8 | { 9 | public class LoginWith2faViewModel 10 | { 11 | [Required] 12 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 13 | [DataType(DataType.Text)] 14 | [Display(Name = "Authenticator code")] 15 | public string TwoFactorCode { get; set; } 16 | 17 | [Display(Name = "Remember this machine")] 18 | public bool RememberMachine { get; set; } 19 | 20 | public bool RememberMe { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Online.Store/Models/AccountViewModels/LoginWithRecoveryCodeViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Models.AccountViewModels 8 | { 9 | public class LoginWithRecoveryCodeViewModel 10 | { 11 | [Required] 12 | [DataType(DataType.Text)] 13 | [Display(Name = "Recovery Code")] 14 | public string RecoveryCode { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Online.Store/Models/AccountViewModels/RegisterViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Models.AccountViewModels 8 | { 9 | public class RegisterViewModel 10 | { 11 | [Required] 12 | [EmailAddress] 13 | [Display(Name = "Email")] 14 | public string Email { get; set; } 15 | 16 | [Required] 17 | [Display(Name = "Username")] 18 | public string Username { get; set; } 19 | 20 | [Required] 21 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 22 | [DataType(DataType.Password)] 23 | [Display(Name = "Password")] 24 | public string Password { get; set; } 25 | 26 | [DataType(DataType.Password)] 27 | [Display(Name = "Confirm password")] 28 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 29 | public string ConfirmPassword { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Online.Store/Models/AccountViewModels/ResetPasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Models.AccountViewModels 8 | { 9 | public class ResetPasswordViewModel 10 | { 11 | [Required] 12 | [EmailAddress] 13 | public string Email { get; set; } 14 | 15 | [Required] 16 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 17 | [DataType(DataType.Password)] 18 | public string Password { get; set; } 19 | 20 | [DataType(DataType.Password)] 21 | [Display(Name = "Confirm password")] 22 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 23 | public string ConfirmPassword { get; set; } 24 | 25 | public string Code { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Online.Store/Models/ApplicationUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | 7 | namespace Online.Store.Models 8 | { 9 | // Add profile data for application users by adding properties to the ApplicationUser class 10 | public class ApplicationUser : IdentityUser 11 | { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Online.Store/Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Online.Store.Models 4 | { 5 | public class ErrorViewModel 6 | { 7 | public string RequestId { get; set; } 8 | 9 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 10 | } 11 | } -------------------------------------------------------------------------------- /Online.Store/Models/ManageViewModels/ChangePasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Models.ManageViewModels 8 | { 9 | public class ChangePasswordViewModel 10 | { 11 | [Required] 12 | [DataType(DataType.Password)] 13 | [Display(Name = "Current password")] 14 | public string OldPassword { get; set; } 15 | 16 | [Required] 17 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 18 | [DataType(DataType.Password)] 19 | [Display(Name = "New password")] 20 | public string NewPassword { get; set; } 21 | 22 | [DataType(DataType.Password)] 23 | [Display(Name = "Confirm new password")] 24 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 25 | public string ConfirmPassword { get; set; } 26 | 27 | public string StatusMessage { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Online.Store/Models/ManageViewModels/EnableAuthenticatorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Online.Store.Models.ManageViewModels 9 | { 10 | public class EnableAuthenticatorViewModel 11 | { 12 | [Required] 13 | [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 14 | [DataType(DataType.Text)] 15 | [Display(Name = "Verification Code")] 16 | public string Code { get; set; } 17 | 18 | [ReadOnly(true)] 19 | public string SharedKey { get; set; } 20 | 21 | public string AuthenticatorUri { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Online.Store/Models/ManageViewModels/ExternalLoginsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authentication; 6 | using Microsoft.AspNetCore.Identity; 7 | 8 | namespace Online.Store.Models.ManageViewModels 9 | { 10 | public class ExternalLoginsViewModel 11 | { 12 | public IList CurrentLogins { get; set; } 13 | 14 | public IList OtherLogins { get; set; } 15 | 16 | public bool ShowRemoveButton { get; set; } 17 | 18 | public string StatusMessage { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Online.Store/Models/ManageViewModels/GenerateRecoveryCodesViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Models.ManageViewModels 8 | { 9 | public class GenerateRecoveryCodesViewModel 10 | { 11 | public string[] RecoveryCodes { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Online.Store/Models/ManageViewModels/IndexViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Models.ManageViewModels 8 | { 9 | public class IndexViewModel 10 | { 11 | public string Username { get; set; } 12 | 13 | public bool IsEmailConfirmed { get; set; } 14 | 15 | [Required] 16 | [EmailAddress] 17 | public string Email { get; set; } 18 | 19 | [Phone] 20 | [Display(Name = "Phone number")] 21 | public string PhoneNumber { get; set; } 22 | 23 | public string StatusMessage { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Online.Store/Models/ManageViewModels/RemoveLoginViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Models.ManageViewModels 8 | { 9 | public class RemoveLoginViewModel 10 | { 11 | public string LoginProvider { get; set; } 12 | public string ProviderKey { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Online.Store/Models/ManageViewModels/SetPasswordViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Models.ManageViewModels 8 | { 9 | public class SetPasswordViewModel 10 | { 11 | [Required] 12 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] 13 | [DataType(DataType.Password)] 14 | [Display(Name = "New password")] 15 | public string NewPassword { get; set; } 16 | 17 | [DataType(DataType.Password)] 18 | [Display(Name = "Confirm new password")] 19 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 20 | public string ConfirmPassword { get; set; } 21 | 22 | public string StatusMessage { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Online.Store/Models/ManageViewModels/TwoFactorAuthenticationViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.Models.ManageViewModels 8 | { 9 | public class TwoFactorAuthenticationViewModel 10 | { 11 | public bool HasAuthenticator { get; set; } 12 | 13 | public int RecoveryCodesLeft { get; set; } 14 | 15 | public bool Is2faEnabled { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Online.Store/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace Online_Store 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | BuildWebHost(args).Run(); 18 | } 19 | 20 | public static IWebHost BuildWebHost(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup() 23 | .Build(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Online.Store/ScaffoldingReadMe.txt: -------------------------------------------------------------------------------- 1 |  2 | ASP.NET MVC core dependencies have been added to the project. 3 | (These dependencies include packages required to enable scaffolding) 4 | 5 | However you may still need to do make changes to your project. 6 | 7 | 1. Suggested changes to Startup class: 8 | 1.1 Add a constructor: 9 | public IConfiguration Configuration { get; } 10 | 11 | public Startup(IConfiguration configuration) 12 | { 13 | Configuration = configuration; 14 | } 15 | 1.2 Add MVC services: 16 | public void ConfigureServices(IServiceCollection services) 17 | { 18 | // Add framework services. 19 | services.AddMvc(); 20 | } 21 | 22 | 1.3 Configure web app to use use Configuration and use MVC routing: 23 | 24 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 25 | { 26 | if (env.IsDevelopment()) 27 | { 28 | app.UseDeveloperExceptionPage(); 29 | } 30 | 31 | app.UseStaticFiles(); 32 | 33 | app.UseMvc(routes => 34 | { 35 | routes.MapRoute( 36 | name: "default", 37 | template: "{controller=Home}/{action=Index}/{id?}"); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /Online.Store/Services/EmailSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Online.Store.Services 7 | { 8 | // This class is used by the application to send email for account confirmation and password reset. 9 | // For more details see https://go.microsoft.com/fwlink/?LinkID=532713 10 | public class EmailSender : IEmailSender 11 | { 12 | public Task SendEmailAsync(string email, string subject, string message) 13 | { 14 | return Task.CompletedTask; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Online.Store/Services/IEmailSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Online.Store.Services 7 | { 8 | public interface IEmailSender 9 | { 10 | Task SendEmailAsync(string email, string subject, string message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Online.Store/ViewModels/OrderViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Online.Store.ViewModels 7 | { 8 | public class OrderViewModel 9 | { 10 | public OrderViewModel() 11 | { 12 | OrderDetails = new List(); 13 | } 14 | public int Id { get; set; } 15 | public string UserId { get; set; } 16 | public DateTime DateCreated { get; set; } 17 | public double GrandTotal { get; set; } 18 | 19 | public ICollection OrderDetails { get; set; } 20 | } 21 | 22 | public class OrderDetailViewModel 23 | { 24 | public int Id { get; set; } 25 | public string ProductId { get; set; } 26 | public string ProductTitle { get; set; } 27 | public double ProductPrice { get; set; } 28 | public string ProductImage { get; set; } 29 | public string ProductCdnImage { get; set; } 30 | public int Quantity { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Online.Store/ViewModels/ProductComponentViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Online.Store.ViewModels 7 | { 8 | public class ProductComponentViewModel 9 | { 10 | public ProductComponentViewModel() 11 | { 12 | this.Medias = new List(); 13 | } 14 | public string ComponentType { get; set; } 15 | public string ComponentTitle { get; set; } 16 | public string ComponentDetail { get; set; } 17 | public List Medias { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Online.Store/ViewModels/ProductMediaViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Online.Store.ViewModels 7 | { 8 | public class ProductMediaViewModel 9 | { 10 | public string Url { get; set; } 11 | public string CdnUrl { get; set; } 12 | public string Type { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Online.Store/ViewModels/ProductViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Online.Store.ViewModels 7 | { 8 | public class ProductViewModel 9 | { 10 | public ProductViewModel() 11 | { 12 | this.Components = new List(); 13 | } 14 | public string Id { get; set; } 15 | public double Price { get; set; } 16 | public string Title { get; set; } 17 | public string Description { get; set; } 18 | public string Image { get; set; } 19 | public string CdnImage { get; set; } 20 | public string Model { get; set; } 21 | public string SKU { get; set; } 22 | public decimal Rating { get; set; } 23 | public int Rates { get; set; } 24 | public List Components { get; set; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Online.Store/ViewModels/ReplyViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Online.Store.Core.DTOs; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace Online.Store.ViewModels 9 | { 10 | public class ReplyViewModel 11 | { 12 | public string Title { get; set; } 13 | 14 | public string Content { get; set; } 15 | 16 | public string MediaDescription { get; set; } 17 | 18 | public string UserId { get; set; } 19 | 20 | public string UserName { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Online.Store/ViewModels/ResultViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Online.Store.ViewModels 7 | { 8 | public class ResultViewModel 9 | { 10 | public Result Result { get; set; } 11 | public string Message { get; set; } 12 | public object Data { get; set; } 13 | } 14 | 15 | public enum Result 16 | { 17 | SUCCESS = 1, 18 | ERROR = 2 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Online.Store/ViewModels/SearchResultViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Online.Store.ViewModels 7 | { 8 | public class SearchResultViewModel 9 | { 10 | public string Id { get; set; } 11 | public string Title { get; set; } 12 | public string Description { get; set; } 13 | public string Image { get; set; } 14 | public string CdnImage { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Online.Store/ViewModels/UserCartViewModel.cs: -------------------------------------------------------------------------------- 1 | using Online.Store.Core.DTOs; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace Online.Store.ViewModels 8 | { 9 | public class UserCartViewModel 10 | { 11 | public CartDTO Cart { get; set; } 12 | public UserViewModel User { get; set; } 13 | public bool UseIdentity { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Online.Store/ViewModels/UserViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace Online.Store.ViewModels 7 | { 8 | public class UserViewModel 9 | { 10 | public string Id { get; set; } 11 | public string Username { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Online.Store/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Planet Scale Store"; 3 | } 4 | 5 | 8 | 9 | Loading... 10 | 11 |
21 | Served by @ViewData["Region"] 22 |
23 | 24 | 25 | @section scripts { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Online.Store/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | 8 | @if (!string.IsNullOrEmpty((string)ViewData["RequestId"])) 9 | { 10 |

11 | Request ID: @ViewData["RequestId"] 12 |

13 | } 14 | 15 |

Development Mode

16 |

17 | Swapping to Development environment will display more detailed information about the error that occurred. 18 |

19 |

20 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 21 |

22 | -------------------------------------------------------------------------------- /Online.Store/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] 7 | 8 | 9 | 10 | 11 | 12 | @RenderBody() 13 | 14 | @RenderSection("scripts", required: false) 15 | 16 | 17 | -------------------------------------------------------------------------------- /Online.Store/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Online_Store 2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 | @addTagHelper *, Microsoft.AspNetCore.SpaServices 4 | -------------------------------------------------------------------------------- /Online.Store/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /Online.Store/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AzureAd": { 3 | "Instance": "https://login.microsoftonline.com/", 4 | "Domain": "", 5 | "TenantId": "", 6 | "ClientId": "", 7 | "CallbackPath": "/signin-oidc" 8 | }, 9 | "Logging": { 10 | "IncludeScopes": false, 11 | "LogLevel": { 12 | "Default": "Warning" 13 | } 14 | }, 15 | "App:PageSize": 10, 16 | "DocumentDB:Endpoint": "-cosmosdb", 17 | "DocumentDB:DatabaseId": "", 18 | "CDN:Endpoint": "https://-endpoint.azureedge.net", 19 | "SearchService:Name": "-search", 20 | "Storage:AccountName": "storage", 21 | "RedisCache:Endpoint": "-rediscache", 22 | "RedisCache:DefaultExpirationMinutes": 60, 23 | "ServiceBus:Namespace": "-servicebus", 24 | "ServiceBus:Queue": "orders", 25 | "Region": "Local", 26 | "UseIdentity": true 27 | } -------------------------------------------------------------------------------- /Online.Store/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "planet-scale-store", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "test": "karma start ClientApp/test/karma.conf.js" 7 | }, 8 | "dependencies": { 9 | "@angular/animations": "4.2.5", 10 | "@angular/common": "4.2.5", 11 | "@angular/compiler": "4.2.5", 12 | "@angular/compiler-cli": "4.2.5", 13 | "@angular/core": "4.2.5", 14 | "@angular/forms": "4.2.5", 15 | "@angular/http": "4.2.5", 16 | "@angular/platform-browser": "4.2.5", 17 | "@angular/platform-browser-dynamic": "4.2.5", 18 | "@angular/platform-server": "4.2.5", 19 | "@angular/router": "4.2.5", 20 | "@ngrx/effects": "^4.0.5", 21 | "@ngrx/store": "^4.0.3", 22 | "@ngrx/store-devtools": "^4.0.0", 23 | "@ngtools/webpack": "1.5.0", 24 | "@types/webpack-env": "1.13.0", 25 | "angular2-notifications": "^0.7.8", 26 | "angular2-router-loader": "^0.3.5", 27 | "angular2-template-loader": "0.6.2", 28 | "aspnet-prerendering": "^3.0.1", 29 | "aspnet-webpack": "^2.0.1", 30 | "awesome-typescript-loader": "3.2.1", 31 | "bootstrap": "3.4.0", 32 | "css": "2.2.1", 33 | "css-loader": "0.28.4", 34 | "es6-shim": "0.35.3", 35 | "event-source-polyfill": "0.0.9", 36 | "expose-loader": "0.7.3", 37 | "extract-text-webpack-plugin": "2.1.2", 38 | "file-loader": "0.11.2", 39 | "font-awesome": "^4.7.0", 40 | "hammerjs": "^2.0.8", 41 | "html-loader": "0.4.5", 42 | "isomorphic-fetch": "2.2.1", 43 | "jquery": "3.2.1", 44 | "json-loader": "0.5.4", 45 | "ng2-completer": "^1.6.3", 46 | "ngx-img-fallback": "^1.2.0", 47 | "ngx-loading": "^1.0.8", 48 | "preboot": "4.5.2", 49 | "raw-loader": "0.5.1", 50 | "reflect-metadata": "0.1.10", 51 | "rxjs": "5.4.2", 52 | "style-loader": "0.18.2", 53 | "to-string-loader": "1.1.5", 54 | "typescript": "2.4.1", 55 | "url-loader": "0.5.9", 56 | "webpack": "2.5.1", 57 | "webpack-hot-middleware": "2.18.2", 58 | "webpack-merge": "4.1.0", 59 | "webpack-node-externals": "^1.6.0", 60 | "zone.js": "0.8.12" 61 | }, 62 | "devDependencies": { 63 | "@types/chai": "4.0.1", 64 | "@types/jasmine": "2.5.53", 65 | "chai": "4.0.2", 66 | "jasmine-core": "2.6.4", 67 | "karma": "1.7.0", 68 | "karma-chai": "0.1.0", 69 | "karma-chrome-launcher": "2.2.0", 70 | "karma-cli": "1.0.1", 71 | "karma-jasmine": "1.1.0", 72 | "karma-webpack": "2.0.3" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Online.Store/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2015", 4 | "moduleResolution": "node", 5 | "target": "es5", 6 | "sourceMap": true, 7 | "experimentalDecorators": true, 8 | "emitDecoratorMetadata": true, 9 | "skipDefaultLibCheck": true, 10 | "skipLibCheck": true, // Workaround for https://github.com/angular/angular/issues/17863. Remove this if you upgrade to a fixed version of Angular. 11 | "strict": true, 12 | "lib": [ "es6", "dom" ], 13 | "types": [ "webpack-env" ] 14 | }, 15 | "exclude": [ "bin", "node_modules" ], 16 | "atom": { "rewriteTsconfig": false } 17 | } 18 | -------------------------------------------------------------------------------- /Online.Store/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/favicon.ico -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/26507/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/26507/1.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/26507/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/26507/2.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/26507/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/26507/3.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/26507/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/26507/4.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/26507/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/26507/5.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/26507/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/26507/6.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/26507/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/26507/7.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/26507/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/26507/8.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/9779B001/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/9779B001/1.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/9779B001/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/9779B001/2.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/9779B001/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/9779B001/3.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/9779B001/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/9779B001/4.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/9779B001/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/9779B001/5.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/9779B001/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/9779B001/6.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DC-ZS70K/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DC-ZS70K/1.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DC-ZS70K/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DC-ZS70K/2.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DC-ZS70K/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DC-ZS70K/3.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DC-ZS70K/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DC-ZS70K/4.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DC-ZS70K/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DC-ZS70K/5.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DC-ZS70K/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DC-ZS70K/6.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DC-ZS70K/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DC-ZS70K/7.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DSC-HX80/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DSC-HX80/1.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DSC-HX80/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DSC-HX80/2.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DSC-HX80/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DSC-HX80/3.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DSC-HX80/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DSC-HX80/4.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DSCW830/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DSCW830/1.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DSCW830/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DSCW830/2.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DSCW830/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DSCW830/3.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/DSCW830/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/DSCW830/4.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/POLSP01W/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/POLSP01W/1.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/POLSP01W/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/POLSP01W/2.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/POLSP01W/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/POLSP01W/3.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/POLSP01W/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/POLSP01W/4.jpg -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/app/schema-active-georeplication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/app/schema-active-georeplication.png -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/app/schema-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/app/schema-architecture.png -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/app/schema-chaining-georeplication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/app/schema-chaining-georeplication.png -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/app/schema-child-resource-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/app/schema-child-resource-group.png -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/app/schema-devops-new-child-resource-groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/app/schema-devops-new-child-resource-groups.png -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/app/schema-distributing-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/app/schema-distributing-data.png -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/app/schema-parent-resource-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/app/schema-parent-resource-group.png -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/app/schema-primary-resource-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/app/schema-primary-resource-group.png -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/app/schema-replicas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/app/schema-replicas.png -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/app/schema-traffic-manager-current-status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/app/schema-traffic-manager-current-status.png -------------------------------------------------------------------------------- /Online.Store/wwwroot/images/app/schema-traffic-manager-new-status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chsakell/planet-scale-azure/4f555c7369b0499f22331edbf11c6b105f61cdd8/Online.Store/wwwroot/images/app/schema-traffic-manager-new-status.png -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '2.0.{build}' 2 | image: Visual Studio 2017 3 | branches: 4 | only: 5 | - master 6 | clone_depth: 1 7 | init: 8 | # Good practise, because Windows 9 | - git config --global core.autocrlf true 10 | 11 | # Install scripts. (runs after repo cloning) 12 | install: 13 | # Get the latest stable version of Node.js or io.js 14 | # - ps: Install-Product node $env:nodejs_version 15 | - cd Online.Store 16 | # install npm modules 17 | - ps: Install-Product node $env:nodejs_version 18 | - npm install 19 | - node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod 20 | before_build: 21 | # Display minimal restore text 22 | - cmd: dotnet restore --verbosity m 23 | build_script: 24 | # output will be in ./Online.Store/bin/Release/netcoreapp2.0/publish/ 25 | - cmd: dotnet publish -c Release 26 | artifacts: 27 | - path: '\Online.Store\bin\Release\netcoreapp2.0\publish' 28 | name: WebSite 29 | type: WebDeployPackage -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ 3 | "Contoso.Ecommerce.AppServices", 4 | "Contoso.Ecommerce.Azure.ElasticScale", 5 | "Contoso.Ecommerce.Core", 6 | "Contoso.Ecommerce.Repository", 7 | "Contoso.Ecommerce.Website" 8 | ] 9 | } -------------------------------------------------------------------------------- /licence: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Christos Sakellarios 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | --------------------------------------------------------------------------------