diff --git a/Sanhe.Abp.Framework.sln b/Sanhe.Abp.Framework.sln index 20d83ae..829f8ba 100644 --- a/Sanhe.Abp.Framework.sln +++ b/Sanhe.Abp.Framework.sln @@ -17,6 +17,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sanhe.Abp.AspNetCore.Mvc.Wr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sanhe.Abp.Wrapper", "modules\common\Sanhe.Abp.Wrapper\Sanhe.Abp.Wrapper.csproj", "{FDC1A6FC-DDAC-4119-9AF3-005F26030B94}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "book-store", "book-store", "{928FDD8C-1EE8-455E-952F-11039B52FE03}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BookStore", "services\book-store\BookStore.csproj", "{64178F61-A488-4182-A409-C32AE51E33A1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,6 +35,10 @@ Global {FDC1A6FC-DDAC-4119-9AF3-005F26030B94}.Debug|Any CPU.Build.0 = Debug|Any CPU {FDC1A6FC-DDAC-4119-9AF3-005F26030B94}.Release|Any CPU.ActiveCfg = Release|Any CPU {FDC1A6FC-DDAC-4119-9AF3-005F26030B94}.Release|Any CPU.Build.0 = Release|Any CPU + {64178F61-A488-4182-A409-C32AE51E33A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64178F61-A488-4182-A409-C32AE51E33A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64178F61-A488-4182-A409-C32AE51E33A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64178F61-A488-4182-A409-C32AE51E33A1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -41,6 +49,8 @@ Global {21B8099B-881D-40AE-888E-FC94E66D90C0} = {F5F5D604-531B-4B57-A88E-C9C5CEEC55D7} {06F1C7AC-3A43-4210-8126-8DA0089A39EE} = {21B8099B-881D-40AE-888E-FC94E66D90C0} {FDC1A6FC-DDAC-4119-9AF3-005F26030B94} = {2A768109-31B7-4C52-928C-3023DAB9F254} + {928FDD8C-1EE8-455E-952F-11039B52FE03} = {BBE3B270-DF4F-47F5-9705-89FCFDE2779F} + {64178F61-A488-4182-A409-C32AE51E33A1} = {928FDD8C-1EE8-455E-952F-11039B52FE03} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AB69BFDE-9DDB-4D16-8CB8-72472C0319CD} diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult.cs index 64790af..21fb982 100644 --- a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult.cs +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult.cs @@ -20,7 +20,7 @@ public class WrapResult : WrapResult public WrapResult( string code, object result, - string message = "OK") : base(code, result, message) + string message = "ok") : base(code, result, message) { } } diff --git a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult`T.cs b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult`T.cs index ee907a2..6cadff6 100644 --- a/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult`T.cs +++ b/modules/common/Sanhe.Abp.Wrapper/Sanhe/Abp/Wrapper/WrapResult`T.cs @@ -41,7 +41,7 @@ public class WrapResult public WrapResult( string code, TResult result, - string message = "OK") + string message = "ok") { Code = code; Result = result; diff --git a/services/book-store/BookStore.csproj b/services/book-store/BookStore.csproj new file mode 100644 index 0000000..79656cc --- /dev/null +++ b/services/book-store/BookStore.csproj @@ -0,0 +1,99 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers + compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native + + + + + + + + + + + + + + + + + + + + diff --git a/services/book-store/BookStoreModule.cs b/services/book-store/BookStoreModule.cs new file mode 100644 index 0000000..4d0499e --- /dev/null +++ b/services/book-store/BookStoreModule.cs @@ -0,0 +1,345 @@ +using BookStore.Data; +using BookStore.Localization; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.OpenApi.Models; +using Sanhe.Abp.AspNetCore.Mvc.Wrapper; +using Sanhe.Abp.Wrapper; +using StackExchange.Redis; +using Volo.Abp; +using Volo.Abp.Account; +using Volo.Abp.Account.Web; +using Volo.Abp.AspNetCore.Authentication.JwtBearer; +using Volo.Abp.AspNetCore.MultiTenancy; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.Localization; +using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; +using Volo.Abp.AspNetCore.Serilog; +using Volo.Abp.AuditLogging.EntityFrameworkCore; +using Volo.Abp.Autofac; +using Volo.Abp.AutoMapper; +using Volo.Abp.Caching.StackExchangeRedis; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.PostgreSql; +using Volo.Abp.FeatureManagement; +using Volo.Abp.FeatureManagement.EntityFrameworkCore; +using Volo.Abp.Identity; +using Volo.Abp.Identity.EntityFrameworkCore; +using Volo.Abp.IdentityServer.EntityFrameworkCore; +using Volo.Abp.Localization; +using Volo.Abp.Localization.ExceptionHandling; +using Volo.Abp.Modularity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.PermissionManagement; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; +using Volo.Abp.PermissionManagement.HttpApi; +using Volo.Abp.PermissionManagement.Identity; +using Volo.Abp.SettingManagement; +using Volo.Abp.SettingManagement.EntityFrameworkCore; +using Volo.Abp.Swashbuckle; +using Volo.Abp.TenantManagement; +using Volo.Abp.TenantManagement.EntityFrameworkCore; +using Volo.Abp.UI.Navigation.Urls; +using Volo.Abp.Validation.Localization; +using Volo.Abp.VirtualFileSystem; + +namespace BookStore; + +[DependsOn( + // ABP Framework packages + typeof(AbpAspNetCoreMvcModule), + typeof(AbpAspNetCoreMultiTenancyModule), + typeof(AbpAutofacModule), + typeof(AbpAutoMapperModule), + //typeof(AbpCachingStackExchangeRedisModule), + typeof(AbpEntityFrameworkCorePostgreSqlModule), + typeof(AbpAspNetCoreMvcUiBasicThemeModule), + typeof(AbpSwashbuckleModule), + typeof(AbpAspNetCoreAuthenticationJwtBearerModule), + typeof(AbpAspNetCoreSerilogModule), + + // Account module packages + typeof(AbpAccountApplicationModule), + typeof(AbpAccountHttpApiModule), + typeof(AbpAccountWebIdentityServerModule), + + // Identity module packages + typeof(AbpPermissionManagementDomainIdentityModule), + typeof(AbpIdentityApplicationModule), + typeof(AbpIdentityHttpApiModule), + typeof(AbpIdentityEntityFrameworkCoreModule), + typeof(AbpIdentityServerEntityFrameworkCoreModule), + + // Audit logging module packages + typeof(AbpAuditLoggingEntityFrameworkCoreModule), + + // Permission Management module packages + typeof(AbpPermissionManagementApplicationModule), + typeof(AbpPermissionManagementHttpApiModule), + typeof(AbpPermissionManagementEntityFrameworkCoreModule), + + // Tenant Management module packages + typeof(AbpTenantManagementApplicationModule), + typeof(AbpTenantManagementHttpApiModule), + typeof(AbpTenantManagementEntityFrameworkCoreModule), + + // Feature Management module packages + typeof(AbpFeatureManagementApplicationModule), + typeof(AbpFeatureManagementEntityFrameworkCoreModule), + typeof(AbpFeatureManagementHttpApiModule), + + // Setting Management module packages + typeof(AbpSettingManagementApplicationModule), + typeof(AbpSettingManagementEntityFrameworkCoreModule), + typeof(AbpSettingManagementHttpApiModule), + + typeof(AbpAspNetCoreMvcWrapperModule) +)] +public class BookStoreModule : AbpModule +{ + /* Single point to enable/disable multi-tenancy */ + private const bool IsMultiTenant = true; + + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.PreConfigure(options => + { + options.AddAssemblyResource( + typeof(BookStoreResource) + ); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + var hostingEnvironment = context.Services.GetHostingEnvironment(); + var configuration = context.Services.GetConfiguration(); + + context.Services.AddAlwaysAllowAuthorization(); + // wrap + Configure(options => + { + options.IsEnabled = true; + options.IgnoreNamespaces.Clear(); + }); + + ConfigureBundles(); + ConfigureMultiTenancy(); + ConfigureUrls(configuration); + ConfigureAutoMapper(); + ConfigureSwagger(context.Services); + ConfigureAutoApiControllers(); + ConfigureVirtualFiles(hostingEnvironment); + ConfigureLocalization(); + ConfigureAuthentication(context.Services, configuration); + ConfigureCors(context, configuration); + //ConfigureDataProtection(context, configuration, hostingEnvironment); + ConfigureEfCore(context); + } + + private void ConfigureBundles() + { + Configure(options => + { + options.StyleBundles.Configure( + BasicThemeBundles.Styles.Global, + bundle => { bundle.AddFiles("/global-styles.css"); } + ); + }); + } + + private void ConfigureMultiTenancy() + { + Configure(options => + { + options.IsEnabled = IsMultiTenant; + }); + } + + private void ConfigureUrls(IConfiguration configuration) + { + Configure(options => + { + options.Applications["MVC"].RootUrl = configuration["App:SelfUrl"]; + options.RedirectAllowedUrls.AddRange(configuration["App:RedirectAllowedUrls"].Split(',')); + + options.Applications["Angular"].RootUrl = configuration["App:ClientUrl"]; + options.Applications["Angular"].Urls[AccountUrlNames.PasswordReset] = "account/reset-password"; + }); + } + + private void ConfigureAuthentication(IServiceCollection services, IConfiguration configuration) + { + services.AddAuthentication() + .AddJwtBearer(options => + { + options.Authority = configuration["AuthServer:Authority"]; + options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); + options.Audience = "BookStore"; + }); + } + + private void ConfigureLocalization() + { + Configure(options => + { + options.Resources + .Add("en") + .AddBaseTypes(typeof(AbpValidationResource)) + .AddVirtualJson("/Localization/BookStore"); + + options.DefaultResourceType = typeof(BookStoreResource); + + options.Languages.Add(new LanguageInfo("en", "en", "English")); + options.Languages.Add(new LanguageInfo("zh-Hans", "zh-Hans", "简体中文")); + }); + + Configure(options => + { + options.MapCodeNamespace("BookStore", typeof(BookStoreResource)); + }); + } + + private void ConfigureVirtualFiles(IWebHostEnvironment hostingEnvironment) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + if (hostingEnvironment.IsDevelopment()) + { + /* Using physical files in development, so we don't need to recompile on changes */ + options.FileSets.ReplaceEmbeddedByPhysical(hostingEnvironment.ContentRootPath); + } + }); + } + + private void ConfigureAutoApiControllers() + { + Configure(options => + { + options.ConventionalControllers.Create(typeof(BookStoreModule).Assembly); + }); + } + + private void ConfigureSwagger(IServiceCollection services) + { + services.AddAbpSwaggerGen( + options => + { + options.SwaggerDoc("v1", new OpenApiInfo { Title = "BookStore API", Version = "v1" }); + options.DocInclusionPredicate((docName, description) => true); + options.CustomSchemaIds(type => type.FullName); + } + ); + } + + private void ConfigureAutoMapper() + { + Configure(options => + { + /* Uncomment `validate: true` if you want to enable the Configuration Validation feature. + * See AutoMapper's documentation to learn what it is: + * https://docs.automapper.org/en/stable/Configuration-validation.html + */ + options.AddMaps(/* validate: true */); + }); + } + + private void ConfigureCors(ServiceConfigurationContext context, IConfiguration configuration) + { + context.Services.AddCors(options => + { + options.AddDefaultPolicy(builder => + { + builder + .WithOrigins( + configuration["App:CorsOrigins"] + .Split(",", StringSplitOptions.RemoveEmptyEntries) + .Select(o => o.RemovePostFix("/")) + .ToArray() + ) + .WithAbpExposedHeaders() + .SetIsOriginAllowedToAllowWildcardSubdomains() + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); + }); + }); + } + + private void ConfigureDataProtection( + ServiceConfigurationContext context, + IConfiguration configuration, + IWebHostEnvironment hostingEnvironment) + { + var dataProtectionBuilder = context.Services.AddDataProtection().SetApplicationName("BookStore"); + if (!hostingEnvironment.IsDevelopment()) + { + var redis = ConnectionMultiplexer.Connect(configuration["Redis:Configuration"]); + dataProtectionBuilder.PersistKeysToStackExchangeRedis(redis, "BookStore-Protection-Keys"); + } + } + + private void ConfigureEfCore(ServiceConfigurationContext context) + { + context.Services.AddAbpDbContext(options => + { + options.AddDefaultRepositories(includeAllEntities: true); + }); + + Configure(options => + { + options.Configure(configurationContext => + { + configurationContext.UseNpgsql(); + }); + }); + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + var app = context.GetApplicationBuilder(); + var env = context.GetEnvironment(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseAbpRequestLocalization(); + + if (!env.IsDevelopment()) + { + app.UseErrorPage(); + } + + app.UseCorrelationId(); + app.UseStaticFiles(); + app.UseRouting(); + app.UseCors(); + app.UseAuthentication(); + app.UseJwtTokenMiddleware(); + + if (IsMultiTenant) + { + app.UseMultiTenancy(); + } + + app.UseUnitOfWork(); + app.UseIdentityServer(); + app.UseAuthorization(); + + app.UseSwagger(); + app.UseAbpSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/v1/swagger.json", "BookStore API"); + }); + + app.UseAuditing(); + app.UseAbpSerilogEnrichers(); + app.UseConfiguredEndpoints(); + } +} diff --git a/services/book-store/Controllers/HomeController.cs b/services/book-store/Controllers/HomeController.cs new file mode 100644 index 0000000..eb98659 --- /dev/null +++ b/services/book-store/Controllers/HomeController.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; + +namespace BookStore.Controllers; + +public class HomeController : AbpController +{ + public ActionResult Index() + { + return Redirect("~/swagger"); + } +} diff --git a/services/book-store/Controllers/WrapController.cs b/services/book-store/Controllers/WrapController.cs new file mode 100644 index 0000000..a530c2a --- /dev/null +++ b/services/book-store/Controllers/WrapController.cs @@ -0,0 +1,46 @@ +using Microsoft.AspNetCore.Mvc; +using Volo.Abp; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.Authorization; + +namespace BookStore.Controllers; + +[Route("api/wrap")] +public class WrapController : AbpControllerBase +{ + [HttpGet("string")] + public Task GetResultAsync() + { + return Task.FromResult("Hello!"); + } + + [HttpGet("person")] + public Task GetPersonAsync() + { + var person = new Person + { + Name = "wwwk", + Age = 18 + }; + + return Task.FromResult(person); + } + + [HttpGet("throw-exception")] + public Task ThrowException() + { + throw new UserFriendlyException("触发了一个异常!"); + } + + [HttpGet("throw-auth-exception")] + public Task ThrowAuthException() + { + throw new AbpAuthorizationException(code: AbpAuthorizationErrorCodes.GivenPolicyHasNotGrantedForGivenResource); + } +} + +public class Person +{ + public string? Name { get; set; } + public int Age { get; set; } +} \ No newline at end of file diff --git a/services/book-store/Data/BookStoreDbContext.cs b/services/book-store/Data/BookStoreDbContext.cs new file mode 100644 index 0000000..9146cb8 --- /dev/null +++ b/services/book-store/Data/BookStoreDbContext.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.AuditLogging.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.FeatureManagement.EntityFrameworkCore; +using Volo.Abp.Identity.EntityFrameworkCore; +using Volo.Abp.IdentityServer.EntityFrameworkCore; +using Volo.Abp.PermissionManagement.EntityFrameworkCore; +using Volo.Abp.SettingManagement.EntityFrameworkCore; +using Volo.Abp.TenantManagement.EntityFrameworkCore; + +namespace BookStore.Data; + +public class BookStoreDbContext : AbpDbContext +{ + public BookStoreDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + /* Include modules to your migration db context */ + + builder.ConfigurePermissionManagement(); + builder.ConfigureSettingManagement(); + builder.ConfigureAuditLogging(); + builder.ConfigureIdentity(); + builder.ConfigureIdentityServer(); + builder.ConfigureFeatureManagement(); + builder.ConfigureTenantManagement(); + + /* Configure your own entities here */ + } +} diff --git a/services/book-store/Data/BookStoreDbMigrationService.cs b/services/book-store/Data/BookStoreDbMigrationService.cs new file mode 100644 index 0000000..12cbef9 --- /dev/null +++ b/services/book-store/Data/BookStoreDbMigrationService.cs @@ -0,0 +1,203 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Identity; +using Volo.Abp.MultiTenancy; +using Volo.Abp.TenantManagement; + +namespace BookStore.Data; + +public class BookStoreDbMigrationService : ITransientDependency +{ + public ILogger Logger { get; set; } + + private readonly IDataSeeder _dataSeeder; + private readonly BookStoreEFCoreDbSchemaMigrator _dbSchemaMigrator; + private readonly ITenantRepository _tenantRepository; + private readonly ICurrentTenant _currentTenant; + + public BookStoreDbMigrationService( + IDataSeeder dataSeeder, + BookStoreEFCoreDbSchemaMigrator dbSchemaMigrator, + ITenantRepository tenantRepository, + ICurrentTenant currentTenant) + { + _dataSeeder = dataSeeder; + _dbSchemaMigrator = dbSchemaMigrator; + _tenantRepository = tenantRepository; + _currentTenant = currentTenant; + + Logger = NullLogger.Instance; + } + + public async Task MigrateAsync() + { + var initialMigrationAdded = AddInitialMigrationIfNotExist(); + + if (initialMigrationAdded) + { + return; + } + + Logger.LogInformation("Started database migrations..."); + + await MigrateDatabaseSchemaAsync(); + await SeedDataAsync(); + + Logger.LogInformation($"Successfully completed host database migrations."); + + var tenants = await _tenantRepository.GetListAsync(includeDetails: true); + + var migratedDatabaseSchemas = new HashSet(); + foreach (var tenant in tenants) + { + using (_currentTenant.Change(tenant.Id)) + { + if (tenant.ConnectionStrings.Any()) + { + var tenantConnectionStrings = tenant.ConnectionStrings + .Select(x => x.Value) + .ToList(); + + if (!migratedDatabaseSchemas.IsSupersetOf(tenantConnectionStrings)) + { + await MigrateDatabaseSchemaAsync(tenant); + + migratedDatabaseSchemas.AddIfNotContains(tenantConnectionStrings); + } + } + + await SeedDataAsync(tenant); + } + + Logger.LogInformation($"Successfully completed {tenant.Name} tenant database migrations."); + } + + Logger.LogInformation("Successfully completed all database migrations."); + Logger.LogInformation("You can safely end this process..."); + } + + private async Task MigrateDatabaseSchemaAsync(Tenant tenant = null) + { + Logger.LogInformation($"Migrating schema for {(tenant == null ? "host" : tenant.Name + " tenant")} database..."); + await _dbSchemaMigrator.MigrateAsync(); + } + + private async Task SeedDataAsync(Tenant tenant = null) + { + Logger.LogInformation($"Executing {(tenant == null ? "host" : tenant.Name + " tenant")} database seed..."); + + await _dataSeeder.SeedAsync(new DataSeedContext(tenant?.Id) + .WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName, IdentityDataSeedContributor.AdminEmailDefaultValue) + .WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName, IdentityDataSeedContributor.AdminPasswordDefaultValue) + ); + } + + private bool AddInitialMigrationIfNotExist() + { + try + { + if (!DbMigrationsProjectExists()) + { + return false; + } + } + catch (Exception) + { + return false; + } + + try + { + if (!MigrationsFolderExists()) + { + AddInitialMigration(); + return true; + } + else + { + return false; + } + } + catch (Exception e) + { + Logger.LogWarning("Couldn't determinate if any migrations exist : " + e.Message); + return false; + } + } + + private bool DbMigrationsProjectExists() + { + return Directory.Exists(GetEntityFrameworkCoreProjectFolderPath()); + } + + private bool MigrationsFolderExists() + { + var dbMigrationsProjectFolder = GetEntityFrameworkCoreProjectFolderPath(); + + return Directory.Exists(Path.Combine(dbMigrationsProjectFolder, "Migrations")); + } + + private void AddInitialMigration() + { + Logger.LogInformation("Creating initial migration..."); + + string argumentPrefix; + string fileName; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + argumentPrefix = "-c"; + fileName = "/bin/bash"; + } + else + { + argumentPrefix = "/C"; + fileName = "cmd.exe"; + } + + var procStartInfo = new ProcessStartInfo(fileName, + $"{argumentPrefix} \"abp create-migration-and-run-migrator \"{GetEntityFrameworkCoreProjectFolderPath()}\" --nolayers\"" + ); + + try + { + Process.Start(procStartInfo); + } + catch (Exception) + { + throw new Exception("Couldn't run ABP CLI..."); + } + } + + private string GetEntityFrameworkCoreProjectFolderPath() + { + var slnDirectoryPath = GetSolutionDirectoryPath(); + + if (slnDirectoryPath == null) + { + throw new Exception("Solution folder not found!"); + } + + return Path.Combine(slnDirectoryPath, "BookStore"); + } + + private string GetSolutionDirectoryPath() + { + var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory()); + + while (Directory.GetParent(currentDirectory.FullName) != null) + { + currentDirectory = Directory.GetParent(currentDirectory.FullName); + + if (Directory.GetFiles(currentDirectory.FullName).FirstOrDefault(f => f.EndsWith(".sln")) != null) + { + return currentDirectory.FullName; + } + } + + return null; + } +} diff --git a/services/book-store/Data/BookStoreEFCoreDbSchemaMigrator.cs b/services/book-store/Data/BookStoreEFCoreDbSchemaMigrator.cs new file mode 100644 index 0000000..c68b0b2 --- /dev/null +++ b/services/book-store/Data/BookStoreEFCoreDbSchemaMigrator.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.DependencyInjection; + +namespace BookStore.Data; + +public class BookStoreEFCoreDbSchemaMigrator : ITransientDependency +{ + private readonly IServiceProvider _serviceProvider; + + public BookStoreEFCoreDbSchemaMigrator( + IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public async Task MigrateAsync() + { + /* We intentionally resolving the BookStoreDbContext + * from IServiceProvider (instead of directly injecting it) + * to properly get the connection string of the current tenant in the + * current scope. + */ + + await _serviceProvider + .GetRequiredService() + .Database + .MigrateAsync(); + } +} diff --git a/services/book-store/Data/IdentityServerDataSeedContributor.cs b/services/book-store/Data/IdentityServerDataSeedContributor.cs new file mode 100644 index 0000000..5d864e2 --- /dev/null +++ b/services/book-store/Data/IdentityServerDataSeedContributor.cs @@ -0,0 +1,271 @@ +using IdentityServer4.Models; +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.IdentityServer.ApiResources; +using Volo.Abp.IdentityServer.ApiScopes; +using Volo.Abp.IdentityServer.Clients; +using Volo.Abp.IdentityServer.IdentityResources; +using Volo.Abp.MultiTenancy; +using Volo.Abp.PermissionManagement; +using Volo.Abp.Uow; +using ApiResource = Volo.Abp.IdentityServer.ApiResources.ApiResource; +using ApiScope = Volo.Abp.IdentityServer.ApiScopes.ApiScope; +using Client = Volo.Abp.IdentityServer.Clients.Client; + +namespace BookStore.Data; + +public class IdentityServerDataSeedContributor : IDataSeedContributor, ITransientDependency +{ + private readonly IApiResourceRepository _apiResourceRepository; + private readonly IApiScopeRepository _apiScopeRepository; + private readonly IClientRepository _clientRepository; + private readonly IIdentityResourceDataSeeder _identityResourceDataSeeder; + private readonly IGuidGenerator _guidGenerator; + private readonly IPermissionDataSeeder _permissionDataSeeder; + private readonly IConfiguration _configuration; + private readonly ICurrentTenant _currentTenant; + + public IdentityServerDataSeedContributor( + IClientRepository clientRepository, + IApiResourceRepository apiResourceRepository, + IApiScopeRepository apiScopeRepository, + IIdentityResourceDataSeeder identityResourceDataSeeder, + IGuidGenerator guidGenerator, + IPermissionDataSeeder permissionDataSeeder, + IConfiguration configuration, + ICurrentTenant currentTenant) + { + _clientRepository = clientRepository; + _apiResourceRepository = apiResourceRepository; + _apiScopeRepository = apiScopeRepository; + _identityResourceDataSeeder = identityResourceDataSeeder; + _guidGenerator = guidGenerator; + _permissionDataSeeder = permissionDataSeeder; + _configuration = configuration; + _currentTenant = currentTenant; + } + + [UnitOfWork] + public virtual async Task SeedAsync(DataSeedContext context) + { + using (_currentTenant.Change(context?.TenantId)) + { + await _identityResourceDataSeeder.CreateStandardResourcesAsync(); + await CreateApiResourcesAsync(); + await CreateApiScopesAsync(); + await CreateClientsAsync(); + } + } + + private async Task CreateApiScopesAsync() + { + await CreateApiScopeAsync("BookStore"); + } + + private async Task CreateApiResourcesAsync() + { + var commonApiUserClaims = new[] {"email", "email_verified", "name", "phone_number", "phone_number_verified", "role"}; + await CreateApiResourceAsync("BookStore", commonApiUserClaims); + } + + private async Task CreateApiResourceAsync(string name, IEnumerable claims) + { + var apiResource = await _apiResourceRepository.FindByNameAsync(name); + if (apiResource == null) + { + apiResource = await _apiResourceRepository.InsertAsync( + new ApiResource( + _guidGenerator.Create(), + name, + name + " API" + ), + autoSave: true + ); + } + + foreach (var claim in claims) + { + if (apiResource.FindClaim(claim) == null) + { + apiResource.AddUserClaim(claim); + } + } + + return await _apiResourceRepository.UpdateAsync(apiResource); + } + + private async Task CreateApiScopeAsync(string name) + { + var apiScope = await _apiScopeRepository.FindByNameAsync(name); + if (apiScope == null) + { + apiScope = await _apiScopeRepository.InsertAsync( + new ApiScope( + _guidGenerator.Create(), + name, + name + " API" + ), + autoSave: true + ); + } + + return apiScope; + } + + private async Task CreateClientsAsync() + { + var commonScopes = new[] + { + "email", + "openid", + "profile", + "role", + "phone", + "address", + "BookStore" + }; + + var configurationSection = _configuration.GetSection("IdentityServer:Clients"); + + // Angular Client + var consoleAndAngularClientId = configurationSection["BookStore_App:ClientId"]; + if (!consoleAndAngularClientId.IsNullOrWhiteSpace()) + { + var webClientRootUrl = configurationSection["BookStore_App:RootUrl"]?.TrimEnd('/'); + + await CreateClientAsync( + name: consoleAndAngularClientId, + scopes: commonScopes, + grantTypes: new[] { "password", "client_credentials", "authorization_code" }, + secret: (configurationSection["BookStore_App:ClientSecret"] ?? "1q2w3e*").Sha256(), + requireClientSecret: false, + redirectUri: webClientRootUrl, + postLogoutRedirectUri: webClientRootUrl, + corsOrigins: new[] { webClientRootUrl.RemovePostFix("/") } + ); + } + + // Swagger Client + var swaggerClientId = configurationSection["BookStore_Swagger:ClientId"]; + if (!swaggerClientId.IsNullOrWhiteSpace()) + { + var swaggerRootUrl = configurationSection["BookStore_Swagger:RootUrl"].TrimEnd('/'); + + await CreateClientAsync( + name: swaggerClientId, + scopes: commonScopes, + grantTypes: new[] { "authorization_code" }, + secret: configurationSection["BookStore_Swagger:ClientSecret"]?.Sha256(), + requireClientSecret: false, + redirectUri: $"{swaggerRootUrl}/swagger/oauth2-redirect.html", + corsOrigins: new[] { swaggerRootUrl.RemovePostFix("/") } + ); + } + } + + private async Task CreateClientAsync( + string name, + IEnumerable scopes, + IEnumerable grantTypes, + string secret = null, + string redirectUri = null, + string postLogoutRedirectUri = null, + string frontChannelLogoutUri = null, + bool requireClientSecret = true, + bool requirePkce = false, + IEnumerable permissions = null, + IEnumerable corsOrigins = null) + { + var client = await _clientRepository.FindByClientIdAsync(name); + if (client == null) + { + client = await _clientRepository.InsertAsync( + new Client( + _guidGenerator.Create(), + name + ) + { + ClientName = name, + ProtocolType = "oidc", + Description = name, + AlwaysIncludeUserClaimsInIdToken = true, + AllowOfflineAccess = true, + AbsoluteRefreshTokenLifetime = 31536000, //365 days + AccessTokenLifetime = 31536000, //365 days + AuthorizationCodeLifetime = 300, + IdentityTokenLifetime = 300, + RequireConsent = false, + FrontChannelLogoutUri = frontChannelLogoutUri, + RequireClientSecret = requireClientSecret, + RequirePkce = requirePkce + }, + autoSave: true + ); + } + + foreach (var scope in scopes) + { + if (client.FindScope(scope) == null) + { + client.AddScope(scope); + } + } + + foreach (var grantType in grantTypes) + { + if (client.FindGrantType(grantType) == null) + { + client.AddGrantType(grantType); + } + } + + if (!secret.IsNullOrEmpty()) + { + if (client.FindSecret(secret) == null) + { + client.AddSecret(secret); + } + } + + if (redirectUri != null) + { + if (client.FindRedirectUri(redirectUri) == null) + { + client.AddRedirectUri(redirectUri); + } + } + + if (postLogoutRedirectUri != null) + { + if (client.FindPostLogoutRedirectUri(postLogoutRedirectUri) == null) + { + client.AddPostLogoutRedirectUri(postLogoutRedirectUri); + } + } + + if (permissions != null) + { + await _permissionDataSeeder.SeedAsync( + ClientPermissionValueProvider.ProviderName, + name, + permissions, + null + ); + } + + if (corsOrigins != null) + { + foreach (var origin in corsOrigins) + { + if (!origin.IsNullOrWhiteSpace() && client.FindCorsOrigin(origin) == null) + { + client.AddCorsOrigin(origin); + } + } + } + + return await _clientRepository.UpdateAsync(client); + } +} diff --git a/services/book-store/Entities/.gitkeep b/services/book-store/Entities/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/services/book-store/Localization/BookStore/ar.json b/services/book-store/Localization/BookStore/ar.json new file mode 100644 index 0000000..1ee9453 --- /dev/null +++ b/services/book-store/Localization/BookStore/ar.json @@ -0,0 +1,8 @@ +{ + "culture": "ar", + "texts": { + "Welcome_Title": "مرحبا", + "Welcome_Text": "هذا هو قالب بدء تشغيل تطبيق ذو طبقة واحدة مبسط لإطار عمل ABP.", + "Menu:Home": "الرئيسية" + } +} diff --git a/services/book-store/Localization/BookStore/cs.json b/services/book-store/Localization/BookStore/cs.json new file mode 100644 index 0000000..524a48e --- /dev/null +++ b/services/book-store/Localization/BookStore/cs.json @@ -0,0 +1,8 @@ +{ + "culture": "cs", + "texts": { + "Welcome_Title": "Vítejte", + "Welcome_Text": "Toto je minimalistická šablona pro spuštění aplikace s jednou vrstvou pro ABP Framework.", + "Menu:Home": "Úvod" + } +} diff --git a/services/book-store/Localization/BookStore/de-DE.json b/services/book-store/Localization/BookStore/de-DE.json new file mode 100644 index 0000000..da374cd --- /dev/null +++ b/services/book-store/Localization/BookStore/de-DE.json @@ -0,0 +1,8 @@ +{ + "culture": "de-DE", + "texts": { + "Welcome_Title": "Willkommen", + "Welcome_Text": "Dies ist eine minimalistische, einschichtige Anwendungsstartvorlage für das ABP-Framework.", + "Menu:Home": "Home" + } +} diff --git a/services/book-store/Localization/BookStore/en-GB.json b/services/book-store/Localization/BookStore/en-GB.json new file mode 100644 index 0000000..cf24737 --- /dev/null +++ b/services/book-store/Localization/BookStore/en-GB.json @@ -0,0 +1,8 @@ +{ + "culture": "en-GB", + "texts": { + "Welcome_Title": "Welcome_Title", + "Welcome_Text": "This is a minimalist, single layer application startup template for the ABP Framework.", + "Menu:Home": "Home" + } +} diff --git a/services/book-store/Localization/BookStore/en.json b/services/book-store/Localization/BookStore/en.json new file mode 100644 index 0000000..3ee1729 --- /dev/null +++ b/services/book-store/Localization/BookStore/en.json @@ -0,0 +1,8 @@ +{ + "culture": "en", + "texts": { + "Welcome_Title": "Welcome", + "Welcome_Text": "This is a minimalist, single layer application startup template for the ABP Framework.", + "Menu:Home": "Home" + } +} diff --git a/services/book-store/Localization/BookStore/es.json b/services/book-store/Localization/BookStore/es.json new file mode 100644 index 0000000..6e138d3 --- /dev/null +++ b/services/book-store/Localization/BookStore/es.json @@ -0,0 +1,8 @@ +{ + "culture": "es", + "texts": { + "Welcome_Title": "Bienvenido", + "Welcome_Text": "Esta es una plantilla de inicio de aplicación minimalista de una sola capa para ABP Framework.", + "Menu:Home": "Inicio" + } +} diff --git a/services/book-store/Localization/BookStore/fi.json b/services/book-store/Localization/BookStore/fi.json new file mode 100644 index 0000000..e963857 --- /dev/null +++ b/services/book-store/Localization/BookStore/fi.json @@ -0,0 +1,8 @@ +{ + "culture": "fi", + "texts": { + "Welcome_Title": "Tervetuloa", + "Welcome_Text": "Tämä on minimalistinen yksikerroksinen sovelluksen käynnistysmalli ABP Frameworkille.", + "Menu:Home": "Koti" + } +} diff --git a/services/book-store/Localization/BookStore/fr.json b/services/book-store/Localization/BookStore/fr.json new file mode 100644 index 0000000..b2c2b62 --- /dev/null +++ b/services/book-store/Localization/BookStore/fr.json @@ -0,0 +1,8 @@ +{ + "culture": "fr", + "texts": { + "Welcome_Title": "Bienvenue", + "Welcome_Text": "Il s'agit d'un modèle de démarrage d'application minimaliste à une seule couche pour le cadre ABP.", + "Menu:Home": "Accueil" + } +} diff --git a/services/book-store/Localization/BookStore/hi.json b/services/book-store/Localization/BookStore/hi.json new file mode 100644 index 0000000..ec16d65 --- /dev/null +++ b/services/book-store/Localization/BookStore/hi.json @@ -0,0 +1,8 @@ +{ + "culture": "hi", + "texts": { + "Welcome_Title": "स्वागत हे", + "Welcome_Text": "यह एबीपी फ्रेमवर्क के लिए एक न्यूनतम, सिंगल लेयर एप्लिकेशन स्टार्टअप टेम्प्लेट है।", + "Menu:Home": "घर" + } +} diff --git a/services/book-store/Localization/BookStore/hu.json b/services/book-store/Localization/BookStore/hu.json new file mode 100644 index 0000000..fa6f372 --- /dev/null +++ b/services/book-store/Localization/BookStore/hu.json @@ -0,0 +1,8 @@ +{ + "culture": "hu", + "texts": { + "Welcome_Title": "Üdvözlöm", + "Welcome_Text": "Ez egy minimalista, egyrétegű alkalmazásindítási sablon az ABP-keretrendszerhez.", + "Menu:Home": "Kezdőlap" + } +} diff --git a/services/book-store/Localization/BookStore/is.json b/services/book-store/Localization/BookStore/is.json new file mode 100644 index 0000000..080c0ba --- /dev/null +++ b/services/book-store/Localization/BookStore/is.json @@ -0,0 +1,8 @@ +{ + "culture": "is", + "texts": { + "Welcome_Title": "Velkomin", + "Welcome_Text": "Þetta er lægstur, eins lags ræsingarsniðmát fyrir ABP Framework.", + "Menu:Home": "Heim" + } +} diff --git a/services/book-store/Localization/BookStore/it.json b/services/book-store/Localization/BookStore/it.json new file mode 100644 index 0000000..57c3cf6 --- /dev/null +++ b/services/book-store/Localization/BookStore/it.json @@ -0,0 +1,8 @@ +{ + "culture": "it", + "texts": { + "Welcome_Title": "Benvenuto", + "Welcome_Text": "Questo è un modello di avvio dell'applicazione minimalista a livello singolo per ABP Framework.", + "Menu:Home": "Home" + } +} diff --git a/services/book-store/Localization/BookStore/nl.json b/services/book-store/Localization/BookStore/nl.json new file mode 100644 index 0000000..525ee02 --- /dev/null +++ b/services/book-store/Localization/BookStore/nl.json @@ -0,0 +1,8 @@ +{ + "culture": "nl", + "texts": { + "Welcome_Title": "Welkom", + "Welcome_Text": "Dit is een minimalistische, enkellaagse applicatie-opstartsjabloon voor het ABP Framework.", + "Menu:Home": "Home" + } +} diff --git a/services/book-store/Localization/BookStore/pl-PL.json b/services/book-store/Localization/BookStore/pl-PL.json new file mode 100644 index 0000000..2ebf70a --- /dev/null +++ b/services/book-store/Localization/BookStore/pl-PL.json @@ -0,0 +1,8 @@ +{ + "culture": "pl-PL", + "texts": { + "Welcome_Title": "Witaj", + "Welcome_Text": "Jest to minimalistyczny, jednowarstwowy szablon uruchamiania aplikacji dla ABP Framework.", + "Menu:Home": "Home" + } +} diff --git a/services/book-store/Localization/BookStore/pt-BR.json b/services/book-store/Localization/BookStore/pt-BR.json new file mode 100644 index 0000000..96e45d3 --- /dev/null +++ b/services/book-store/Localization/BookStore/pt-BR.json @@ -0,0 +1,8 @@ +{ + "culture": "pt-BR", + "texts": { + "Welcome_Title": "Seja bem-vindo!", + "Welcome_Text": "Este é um modelo de inicialização de aplicativo de camada única minimalista para o ABP Framework.", + "Menu:Home": "Principal" + } +} diff --git a/services/book-store/Localization/BookStore/ro-RO.json b/services/book-store/Localization/BookStore/ro-RO.json new file mode 100644 index 0000000..48102a7 --- /dev/null +++ b/services/book-store/Localization/BookStore/ro-RO.json @@ -0,0 +1,8 @@ +{ + "culture": "ro-RO", + "texts": { + "Welcome_Title": "Bun venit", + "Welcome_Text": "Acesta este un șablon de pornire a aplicației minimaliste, cu un singur strat, pentru Cadrul ABP.", + "Menu:Home": "Acasă" + } +} diff --git a/services/book-store/Localization/BookStore/ru.json b/services/book-store/Localization/BookStore/ru.json new file mode 100644 index 0000000..eb4cc96 --- /dev/null +++ b/services/book-store/Localization/BookStore/ru.json @@ -0,0 +1,8 @@ +{ + "culture": "ru", + "texts": { + "Welcome_Title": "Bine ati venit", + "Welcome_Text": "Acesta este un șablon de pornire a aplicației minimaliste, cu un singur strat, pentru Cadrul ABP.", + "Menu:Home": "Главная" + } +} diff --git a/services/book-store/Localization/BookStore/sk.json b/services/book-store/Localization/BookStore/sk.json new file mode 100644 index 0000000..227d0a7 --- /dev/null +++ b/services/book-store/Localization/BookStore/sk.json @@ -0,0 +1,8 @@ +{ + "culture": "sk", + "texts": { + "Welcome_Title": "Vitajte", + "Welcome_Text": "Toto je minimalistická šablóna na spustenie aplikácie s jednou vrstvou pre rámec ABP.", + "Menu:Home": "Domov" + } +} diff --git a/services/book-store/Localization/BookStore/sl.json b/services/book-store/Localization/BookStore/sl.json new file mode 100644 index 0000000..29876af --- /dev/null +++ b/services/book-store/Localization/BookStore/sl.json @@ -0,0 +1,8 @@ +{ + "culture": "sl", + "texts": { + "Welcome_Title": "Dobrodošli", + "Welcome_Text": "To je minimalistična enoslojna predloga za zagon aplikacije za ABP Framework.", + "Menu:Home": "Domov" + } +} diff --git a/services/book-store/Localization/BookStore/tr.json b/services/book-store/Localization/BookStore/tr.json new file mode 100644 index 0000000..6cbdac7 --- /dev/null +++ b/services/book-store/Localization/BookStore/tr.json @@ -0,0 +1,8 @@ +{ + "culture": "tr", + "texts": { + "Welcome_Title": "Hoşgeldiniz", + "Welcome_Text": "Bu proje tek katmanlı ABP uygulamaları yapmak için bir başlangıç şablonudur.", + "Menu:Home": "Ana sayfa" + } +} diff --git a/services/book-store/Localization/BookStore/vi.json b/services/book-store/Localization/BookStore/vi.json new file mode 100644 index 0000000..f2e55bd --- /dev/null +++ b/services/book-store/Localization/BookStore/vi.json @@ -0,0 +1,8 @@ +{ + "culture": "vi", + "texts": { + "Welcome_Title": "Chào mừng bạn", + "Welcome_Text": "Đây là một mẫu khởi động ứng dụng lớp đơn, tối giản cho ABP Framework.", + "Menu:Home": "Trang chủ" + } +} diff --git a/services/book-store/Localization/BookStore/zh-Hans.json b/services/book-store/Localization/BookStore/zh-Hans.json new file mode 100644 index 0000000..6a66abe --- /dev/null +++ b/services/book-store/Localization/BookStore/zh-Hans.json @@ -0,0 +1,8 @@ +{ + "culture": "zh-Hans", + "texts": { + "Welcome_Title": "欢迎", + "Welcome_Text": "这是ABP框架的极简单层应用程序启动模板.", + "Menu:Home": "首页" + } +} diff --git a/services/book-store/Localization/BookStore/zh-Hant.json b/services/book-store/Localization/BookStore/zh-Hant.json new file mode 100644 index 0000000..7b4872c --- /dev/null +++ b/services/book-store/Localization/BookStore/zh-Hant.json @@ -0,0 +1,8 @@ +{ + "culture": "zh-Hant", + "texts": { + "Welcome_Title": "歡迎", + "Welcome_Text": "這是 ABP 框架的極簡單層應用程序啟動模板.", + "Menu:Home": "首頁" + } +} diff --git a/services/book-store/Localization/BookStoreResource.cs b/services/book-store/Localization/BookStoreResource.cs new file mode 100644 index 0000000..d3c5a4b --- /dev/null +++ b/services/book-store/Localization/BookStoreResource.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Localization; + +namespace BookStore.Localization; + +[LocalizationResourceName("BookStore")] +public class BookStoreResource +{ + +} \ No newline at end of file diff --git a/services/book-store/ObjectMapping/BookStoreAutoMapperProfile.cs b/services/book-store/ObjectMapping/BookStoreAutoMapperProfile.cs new file mode 100644 index 0000000..34d6a99 --- /dev/null +++ b/services/book-store/ObjectMapping/BookStoreAutoMapperProfile.cs @@ -0,0 +1,11 @@ +using AutoMapper; + +namespace BookStore.ObjectMapping; + +public class BookStoreAutoMapperProfile : Profile +{ + public BookStoreAutoMapperProfile() + { + /* Create your AutoMapper object mappings here */ + } +} diff --git a/services/book-store/Program.cs b/services/book-store/Program.cs new file mode 100644 index 0000000..81b4317 --- /dev/null +++ b/services/book-store/Program.cs @@ -0,0 +1,77 @@ +using System; +using BookStore.Data; +using Serilog; +using Serilog.Events; + +namespace BookStore; + +public class Program +{ + public async static Task Main(string[] args) + { + AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true); + var loggerConfiguration = new LoggerConfiguration() +#if DEBUG + .MinimumLevel.Debug() +#else + .MinimumLevel.Information() +#endif + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning) + .Enrich.FromLogContext() +#if DEBUG + .WriteTo.Async(c => c.File("Logs/logs.txt")) + .WriteTo.Async(c => c.Console()); +#else + .WriteTo.Async(c => c.File("Logs/logs.txt")); +#endif + if (IsMigrateDatabase(args)) + { + loggerConfiguration.MinimumLevel.Override("Volo.Abp", LogEventLevel.Warning); + loggerConfiguration.MinimumLevel.Override("Microsoft", LogEventLevel.Warning); + loggerConfiguration.MinimumLevel.Override("IdentityServer4.Startup", LogEventLevel.Warning); + } + + Log.Logger = loggerConfiguration.CreateLogger(); + + try + { + var builder = WebApplication.CreateBuilder(args); + builder.Host.AddAppSettingsSecretsJson() + .UseAutofac() + .UseSerilog(); + await builder.AddApplicationAsync(); + var app = builder.Build(); + await app.InitializeApplicationAsync(); + + if (IsMigrateDatabase(args)) + { + await app.Services.GetRequiredService().MigrateAsync(); + return 0; + } + + Log.Information("Starting BookStore."); + await app.RunAsync(); + return 0; + } + catch (Exception ex) + { + if (ex.GetType().Name.Equals("StopTheHostException", StringComparison.Ordinal)) + { + throw; + } + + Log.Fatal(ex, "BookStore terminated unexpectedly!"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + + private static bool IsMigrateDatabase(string[] args) + { + return args.Any(x => x.Contains("--migrate-database", StringComparison.OrdinalIgnoreCase)); + } +} diff --git a/services/book-store/Properties/launchSettings.json b/services/book-store/Properties/launchSettings.json new file mode 100644 index 0000000..c321a22 --- /dev/null +++ b/services/book-store/Properties/launchSettings.json @@ -0,0 +1,20 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "https://localhost:44357", + "sslPort": 44357 + } + }, + "profiles": { + "BookStore": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "https://localhost:44357", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/services/book-store/Services/Dtos/.gitkeep b/services/book-store/Services/Dtos/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/services/book-store/appsettings.json b/services/book-store/appsettings.json new file mode 100644 index 0000000..13256ff --- /dev/null +++ b/services/book-store/appsettings.json @@ -0,0 +1,37 @@ +{ + "App": { + "SelfUrl": "https://localhost:44357", + "ClientUrl": "http://localhost:4200", + "CorsOrigins": "https://*.BookStore.com,http://localhost:4200", + "RedirectAllowedUrls": "http://localhost:4200" + }, + "ConnectionStrings": { + "Default": "Host=localhost;Port=5432;Database=BookStore;User ID=postgres;Password=123456;" + }, + "Redis": { + "Configuration": "127.0.0.1" + }, + "AuthServer": { + "Authority": "https://localhost:44357", + "RequireHttpsMetadata": "false", + "SwaggerClientId": "BookStore_Swagger", + "SwaggerClientSecret": "1q2w3e*" + }, + "StringEncryption": { + "DefaultPassPhrase": "Q4ki8okY7ZyMNWDw" + }, + "IdentityServer": { + "Clients": { + "BookStore_App": { + "ClientId": "BookStore_App", + "ClientSecret": "1q2w3e*", + "RootUrl": "http://localhost:4200" + }, + "BookStore_Swagger": { + "ClientId": "BookStore_Swagger", + "ClientSecret": "1q2w3e*", + "RootUrl": "https://localhost:44357" + } + } + } +} diff --git a/services/book-store/appsettings.secrets.json b/services/book-store/appsettings.secrets.json new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/services/book-store/appsettings.secrets.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/services/book-store/package.json b/services/book-store/package.json new file mode 100644 index 0000000..ccee8c6 --- /dev/null +++ b/services/book-store/package.json @@ -0,0 +1,8 @@ +{ + "version": "1.0.0", + "name": "my-app", + "private": true, + "dependencies": { + "@abp/aspnetcore.mvc.ui.theme.basic": "^5.2.0-rc.2" + } +} \ No newline at end of file diff --git a/services/book-store/tempkey.jwk b/services/book-store/tempkey.jwk new file mode 100644 index 0000000..60a0cf3 --- /dev/null +++ b/services/book-store/tempkey.jwk @@ -0,0 +1 @@ +{"AdditionalData":{},"Alg":"RS256","Crv":null,"D":"PqIhXe397CUD48Ulbw-kT8MwuWFckXy3hGj-huiX63FpBSRCRd9cmV81h5WI0RX6i0nrKZ2go5cakNgIqx_8gFoOP5N5j8umcU32UtwXePilRjP9RrY_9ub-P0iSmqIEQEZSOg93W7sOVL9jnvd0BLBavqOmlErz9JhU3QU9ebY3lDSdTHZWqYSVb6yeXjZGCRYZgkCgVYx1CiKkFRA5MEEM_WUBhLxVhNmbvFbwlp54PPaBBBEA3RYilv8aK0RBmhdgKRLpfMegEeM3yJh2D92EV2lx3DEhycia72AuqFQ-ZEZBAXVIjxcA8YjtsJXhaOqLnd30EbXHA-sP81kAcQ","DP":"OL_P-Fktuq1YC4YzLuhbRuO0QnkV2xYqPPYNTFJenhOafyPzT2yIAFlJooLcZX6Fe10yFsS918ozn50huIxdfgxrNw2I011-sXYGNuhWnej7OdIUnJkXdSo2Ub8mJpro3iN_524P5cxda9MD5bQAKN1SmgSDHsOZZJALVJBO4dE","DQ":"C2I3Kqu0zaLtYl4OuJxyiMtA2UmK88yFTnDm4pvSWj9XXiiBs2VYLwGevhDTcYI1gT8zar4xeUoWimHzSQxbOcp7EXsjqo_eMTt_t7gwjDRs9tSOu0_mTUhisuM996wAa-QVVzvonxtVPyzHSuoXoxNHfRAUDl1MPVcqV9imTJs","E":"AQAB","K":null,"KeyId":"E845B5C99290199A39E6D83B31E223FA","Kid":"E845B5C99290199A39E6D83B31E223FA","Kty":"RSA","N":"qjXtEBYJMcDnFV59Tk9wTd516-U6Pq_NDujOBZh3ccSvsKc0-NdkAST45WjBscXzh6ByAQcHss1oqrj6YttvzfcReA6IrPVSz1fvBi67eoCjuBVv_94aCoT1fmUpxgv2V9ZqEp23goJ2oppkyTcE7VIoKGgAQREzE4kJ3YssMPYNfR9g_14DbPTMkDYfxfGI7j4wjAOFFNPlWfBToyH7U7dgBtBeu-5NYxFrBZ_Ze0tOoJ20D_u9UZ93rAi7FVhY5787MltstmJ2R-ZNWgZjBad8s_xA6vXHP_7rBh-taAu4ID23U2DeL8w_4XncAPDNdQAZmDJ8_BcN_0uvkaVPNQ","Oth":null,"P":"05r8Rgv5TEAghb4HiCZeI6QcDi1VAXTzHI1f7r_0lliulzk_jc48i9iHGt2cJTaTba_Ln2LDAhkaIKMmptUXVvQiEzy02yeH93Iv9myQHORIwDkSvapMadOn2aCRCa_TJ3K_Bem8Ac9XKV8x4dG1nOtPOchCstfrJRM5g0HiqtM","Q":"zeuwW8cnB0JHvYpbn0dgZZf2tgwHcYNLCbHxsTGZsI6QZJxKC2EgmVFPwKiPLh2_qUegCkns4qUwt7_1eLtoqEplCmbR-rz_TO0zdKcO7-NA4jfud5nMgIIDMvECiaSSrpCoHy204823HXeEv3G6-jMyi7v2kBmfGZt_5RQByNc","QI":"aknrgXL1ZUNAbK4TcVPjG6j-9iCJTsV5LYtzud0DW1usLd67k_XSrCptK3ova__OfnnlzzJpqtCL1LFxk-WTn4ZVT8E1YcjYeaW9DDUQ80DtpYXaL0mv1P8b7ma7FE-m4nuknZeB6D5pYKXT7Tm2tvOHpYn38zzDpt7elFoohVU","Use":null,"X":null,"X5t":null,"X5tS256":null,"X5u":null,"Y":null,"KeySize":2048,"HasPrivateKey":true,"CryptoProviderFactory":{"CryptoProviderCache":{},"CustomCryptoProvider":null,"CacheSignatureProviders":true,"SignatureProviderObjectPoolCacheSize":32}} \ No newline at end of file diff --git a/services/book-store/wwwroot/global-styles.css b/services/book-store/wwwroot/global-styles.css new file mode 100644 index 0000000..06f5063 --- /dev/null +++ b/services/book-store/wwwroot/global-styles.css @@ -0,0 +1 @@ +/* Your Global Styles */