diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index cb79af1665..2a27cce735 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -86,6 +86,11 @@ "AreYouSureYouWantToDeleteAllComputers": "Are you sure you want to delete all computers?", "DeleteAll": "Delete all", "DoYouWantToCreateNewUser": "Do you want to create new user?", - "MasterModules": "Master Modules" + "MasterModules": "Master Modules", + "OrganizationName": "Organization name", + "OrganizationNamePlaceholder": "Organization name...", + "UsernameOrEmail": "Username or email", + "UsernameOrEmailPlaceholder": "Username or email...", + "Member": "Member" } } \ No newline at end of file diff --git a/common.DotSettings b/common.DotSettings index cb0b2c919f..0eb4875d49 100644 --- a/common.DotSettings +++ b/common.DotSettings @@ -20,4 +20,5 @@ False False SQL + True \ No newline at end of file diff --git a/docs/en/API/Dynamic-CSharp-API-Clients.md b/docs/en/API/Dynamic-CSharp-API-Clients.md index 3c3c2ec26c..460623440f 100644 --- a/docs/en/API/Dynamic-CSharp-API-Clients.md +++ b/docs/en/API/Dynamic-CSharp-API-Clients.md @@ -69,7 +69,7 @@ public class MyClientAppModule : AbpModule } ```` -See the "RemoteServiceOptions" section below for more detailed configuration. +See the "AbpRemoteServiceOptions" section below for more detailed configuration. ## Usage @@ -104,7 +104,7 @@ While you can inject `IBookAppService` like above to use the client proxy, you c ## Configuration -### RemoteServiceOptions +### AbpRemoteServiceOptions `AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example: diff --git a/docs/en/Authorization.md b/docs/en/Authorization.md index 1bceda951f..888d907b59 100644 --- a/docs/en/Authorization.md +++ b/docs/en/Authorization.md @@ -339,10 +339,10 @@ A permission value provider should return one of the following values from the ` - `PermissionGrantResult.Prohibited` is returned to prohibit the user for the permission. If any of the providers return `Prohibited`, the result will always be `Prohibited`. Doesn't matter what other providers return. - `PermissionGrantResult.Undefined` is returned if this value provider could not decide about the permission value. Return this to let other providers check the permission. -Once a provider is defined, it should be added to the `PermissionOptions` as shown below: +Once a provider is defined, it should be added to the `AbpPermissionOptions` as shown below: ```csharp -Configure(options => +Configure(options => { options.ValueProviders.Add(); }); diff --git a/docs/en/Best-Practices/Entity-Framework-Core-Integration.md b/docs/en/Best-Practices/Entity-Framework-Core-Integration.md index 4f7e20dd70..f487f9d61d 100644 --- a/docs/en/Best-Practices/Entity-Framework-Core-Integration.md +++ b/docs/en/Best-Practices/Entity-Framework-Core-Integration.md @@ -89,13 +89,15 @@ public static class IdentityDbContextModelBuilderExtensions builder.Entity(b => { - b.ToTable(options.TablePrefix + "Users", options.Schema); + b.ToTable(options.TablePrefix + "Users", options.Schema); + b.ConfigureByConvention(); //code omitted for brevity }); builder.Entity(b => { b.ToTable(options.TablePrefix + "UserClaims", options.Schema); + b.ConfigureByConvention(); //code omitted for brevity }); @@ -104,10 +106,11 @@ public static class IdentityDbContextModelBuilderExtensions } ```` -* **Do** create a **configuration options** class by inheriting from the `ModelBuilderConfigurationOptions`. Example: +* **Do** call `b.ConfigureByConvention();` for each entity mapping (as shown above). +* **Do** create a **configuration options** class by inheriting from the `AbpModelBuilderConfigurationOptions`. Example: ````C# -public class IdentityModelBuilderConfigurationOptions : ModelBuilderConfigurationOptions +public class IdentityModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions { public IdentityModelBuilderConfigurationOptions() : base(AbpIdentityConsts.DefaultDbTablePrefix, AbpIdentityConsts.DefaultDbSchema) diff --git a/docs/en/Best-Practices/MongoDB-Integration.md b/docs/en/Best-Practices/MongoDB-Integration.md index 76b6e8561f..74a0e429f1 100644 --- a/docs/en/Best-Practices/MongoDB-Integration.md +++ b/docs/en/Best-Practices/MongoDB-Integration.md @@ -90,11 +90,11 @@ public static class AbpIdentityMongoDbContextExtensions } ``` -- **Do** create a **configuration options** class by inheriting from the `MongoModelBuilderConfigurationOptions`. Example: +- **Do** create a **configuration options** class by inheriting from the `AbpMongoModelBuilderConfigurationOptions`. Example: ```c# public class IdentityMongoModelBuilderConfigurationOptions - : MongoModelBuilderConfigurationOptions + : AbpMongoModelBuilderConfigurationOptions { public IdentityMongoModelBuilderConfigurationOptions() : base(AbpIdentityConsts.DefaultDbTablePrefix) diff --git a/docs/en/CLI.md b/docs/en/CLI.md index a347599135..f0fd3ae7bd 100644 --- a/docs/en/CLI.md +++ b/docs/en/CLI.md @@ -60,6 +60,7 @@ abp new Acme.BookStore * `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version. * `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D\localTemplate` or `https://.zip`). * `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder. +* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. You can set your own connection string if you don't want to use the default. Be aware that the default database provider is `SQL Server`, therefore you can only enter connection string for SQL Server! ### add-package diff --git a/docs/en/Customizing-Application-Modules-Extending-Entities.md b/docs/en/Customizing-Application-Modules-Extending-Entities.md index def121910e..be28465ff7 100644 --- a/docs/en/Customizing-Application-Modules-Extending-Entities.md +++ b/docs/en/Customizing-Application-Modules-Extending-Entities.md @@ -4,7 +4,7 @@ In some cases, you may want to add some additional properties (and database fiel ## Extra Properties -[Extra properties](Entities.md) is a way of storing some additional data on an entity without changing it. The entity should implement the `IHasExtraProperties` interface to allow it. All the aggregate root entities defined in the pre-built modules implement the `IHasExtraProperties` interface, so you can store extra properties on these entities. +[Extra properties](Entities.md) is a way of storing some additional data on an entity without changing it. The entity should implement the `IHasExtraProperties` interface to allow it. All the aggregate root entities defined in the pre-built modules implement the `IHasExtraProperties` interface, so you can store extra properties on these objects. Example: @@ -25,7 +25,36 @@ Extra properties are stored as a single `JSON` formatted string value in the dat See the [entities document](Entities.md) for more about the extra properties system. -> It is possible to perform a **business logic** based on the value of an extra property. You can **override** a service method and get or set the value as shown above. Overriding services will be discussed below. +> It is possible to perform a **business logic** based on the value of an extra property. You can [override a service method](Customizing-Application-Modules-Overriding-Services.md), then get or set the value as shown above. + +## Entity Extensions (EF Core) + +As mentioned above, all extra properties of an entity are stored as a single JSON object in the database table. This is not so natural especially when you want to; + +* Create **indexes** and **foreign keys** for an extra property. +* Write **SQL** or **LINQ** using the extra property (search table by the property value, for example). +* Creating your **own entity** maps to the same table, but defines an extra property as a **regular property** in the entity (see the [EF Core migration document](Entity-Framework-Core-Migrations.md) for more). + +To overcome the difficulties described above, ABP Framework entity extension system for the Entity Framework Core that allows you to use the same extra properties API defined above, but store a desired property as a separate field in the database table. + +Assume that you want to add a `SocialSecurityNumber` to the `IdentityUser` entity of the [Identity Module](Modules/Identity.md). You can use the `ObjectExtensionManager`: + +````csharp +ObjectExtensionManager.Instance + .MapEfCoreProperty( + "SocialSecurityNumber", + b => { b.HasMaxLength(32); } + ); +```` + +* You provide the `IdentityUser` as the entity name, `string` as the type of the new property, `SocialSecurityNumber` as the property name (also, the field name in the database table). +* You also need to provide an action that defines the database mapping properties using the [EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties). + +> This code part must be executed before the related `DbContext` used. The [application startup template](Startup-Templates/Application.md) defines a static class named `YourProjectNameEntityExtensions`. You can define your extensions in this class to ensure that it is executed in the proper time. Otherwise, you should handle it yourself. + +Once you define an entity extension, you then need to use the standard [Add-Migration](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#add-migration) and [Update-Database](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/powershell#update-database) commands of the EF Core to create a code first migration class and update your database. + +You can then use the same extra properties system defined in the previous section to manipulate the property over the entity. ## Creating a New Entity Maps to the Same Database Table/Collection @@ -146,4 +175,5 @@ public class MyDistributedIdentityUserCreatedEventHandler : ## See Also +* [Migration System for the EF Core](Entity-Framework-Core-Migrations.md) * [Customizing the Existing Modules](Customizing-Application-Modules-Guide.md) \ No newline at end of file diff --git a/docs/en/Entities.md b/docs/en/Entities.md index f0e6c7f301..8a234b94a5 100644 --- a/docs/en/Entities.md +++ b/docs/en/Entities.md @@ -373,16 +373,19 @@ So, you can directly use the `ExtraProperties` property to use the dictionary A The way to store this dictionary in the database depends on the database provider you're using. -* For [Entity Framework Core](Entity-Framework-Core.md), it is stored in a single `ExtraProperties` field as a `JSON` string. Serializing to `JSON` and deserializing from the `JSON` are automatically done by the ABP Framework using the [value conversions](https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions) system of the EF Core. +* For [Entity Framework Core](Entity-Framework-Core.md), here are two type of configurations; + * By default, it is stored in a single `ExtraProperties` field as a `JSON` string (that means all extra properties stored in a single database table field). Serializing to `JSON` and deserializing from the `JSON` are automatically done by the ABP Framework using the [value conversions](https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions) system of the EF Core. + * If you want, you can use the `ObjectExtensionManager` to define a separate table field for a desired extra property. Properties those are not configured through the `ObjectExtensionManager` will continue to use a single `JSON` field as described above. This feature is especially useful when you are using a pre-built [application module](Modules/Index.md) and want to [extend its entities](Customizing-Application-Modules-Extending-Entities.md). See the [EF Core integration document](Entity-Framework-Core.md) to learn how to use the `ObjectExtensionManager`. * For [MongoDB](MongoDB.md), it is stored as a **regular field**, since MongoDB naturally supports this kind of [extra elements](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/#supporting-extra-elements) system. ### Discussion for the Extra Properties -Extra Properties system is especially useful if you are using a **re-usable module** that defines an entity inside and you want to get/set some data related to this entity in an easy way. You normally **don't need** to this system for your own entities, because it has the following drawbacks: +Extra Properties system is especially useful if you are using a **re-usable module** that defines an entity inside and you want to get/set some data related to this entity in an easy way. -* It is **not fully type safe**. +You normally **don't need** to this system for your own entities, because it has the following drawbacks: + +* It is **not fully type safe** since it works with strings as property names. * It is **not easy to [auto map](Object-To-Object-Mapping.md)** these properties from/to other objects. -* It **doesn't create fields** in the database table for EF Core, so it will not be easy to create indexes or search/order by this field in the database side. ### Extra Properties Behind Entities diff --git a/docs/en/Entity-Framework-Core-Migrations.md b/docs/en/Entity-Framework-Core-Migrations.md index 23b17293f1..fbe0f59d70 100644 --- a/docs/en/Entity-Framework-Core-Migrations.md +++ b/docs/en/Entity-Framework-Core-Migrations.md @@ -93,7 +93,7 @@ From the database point of view, there are three important projects those will b This project has the `DbContext` class (`BookStoreDbContext` for this sample) of your application. -**Every module uses its own `DbContext` class** to access to the database. Likewise, your application has its own `DbContext`. You typically use this `DbContext` in your application code (in your custom [repositories](Repositories.md) if you follow the best practices). It is almost an empty `DbContext` since your application don't have any entities at the beginning, except the pre-defined `AppUser` entity: +**Every module uses its own `DbContext` class** to access to the database. Likewise, your application has its own `DbContext`. You typically use this `DbContext` in your application code (in your [repositories](Repositories.md) if you follow the best practices). It is almost an empty `DbContext` since your application don't have any entities at the beginning, except the pre-defined `AppUser` entity: ````csharp [ConnectionStringName("Default")] @@ -117,15 +117,15 @@ public class BookStoreDbContext : AbpDbContext builder.Entity(b => { - //Sharing the same table "AbpUsers" with the IdentityUser - b.ToTable("AbpUsers"); - - //Configure base properties + //Sharing the same Users table with the IdentityUser + b.ToTable(AbpIdentityDbProperties.DbTablePrefix + "Users"); + b.ConfigureByConvention(); b.ConfigureAbpUser(); - //Moved customization of the "AbpUsers" table to an extension method - b.ConfigureCustomUserProperties(); + /* Configure mappings for your additional properties + * Also see the MyProjectNameEntityExtensions class + */ }); /* Configure your own tables/entities inside the ConfigureBookStore method */ @@ -188,12 +188,6 @@ public class BookStoreMigrationsDbContext : AbpDbContext(b => - { - b.ConfigureCustomUserProperties(); - }); - /* Configure your own tables/entities inside the ConfigureBookStore method */ builder.ConfigureBookStore(); } @@ -274,7 +268,7 @@ In this way, the mapping configuration of a module can be shared between `DbCont You may want to **reuse a table** of a depended module in your application. In this case, you have two options: -1. You can **directly use the entity** defined by the module. +1. You can **directly use the entity** defined by the module (you can still [extend the entity](Customizing-Application-Modules-Extending-Entities.md) in some level). 2. You can **create a new entity** mapping to the same database table. ###### Use the Entity Defined by a Module @@ -376,10 +370,8 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity(b => { b.ToTable("AbpRoles"); - b.ConfigureByConvention(); - - b.ConfigureCustomRoleProperties(); + b.Property(x => x.Title).HasMaxLength(128); }); ... @@ -395,69 +387,45 @@ We added the following lines: ````csharp builder.Entity(b => { - b.ToTable("AbpRoles"); - + b.ToTable("AbpRoles"); b.ConfigureByConvention(); - - b.ConfigureCustomRoleProperties(); + b.Property(x => x.Title).HasMaxLength(128); }); ```` * It maps to the same `AbpRoles` table shared with the `IdentityRole` entity. * `ConfigureByConvention()` configures the standard/base properties (like `TenantId`) and recommended to always call it. -`ConfigureCustomRoleProperties()` has not exists yet. Define it inside the `BookStoreDbContextModelCreatingExtensions` class (near to your `DbContext` in the `.EntityFrameworkCore` project): - -````csharp -public static void ConfigureCustomRoleProperties(this EntityTypeBuilder b) - where TRole : class, IEntity -{ - b.Property(nameof(AppRole.Title)).HasMaxLength(128); -} -```` - -* This method only defines the **custom properties** of your entity. -* Unfortunately, we can not utilize the fully **type safety** here (by referencing the `AppRole` entity). The best we can do is to use the `Title` name as type safe. This is because of EF Core migration system can not map two unrelated entity classes to the same database table. +You've configured the custom property for your `DbContext` that is used by your application on the runtime. We also need to configure the `MigrationsDbContext`. -You've configured the custom property for your `DbContext` used by your application on the runtime. We also need to configure the `MigrationsDbContext`. - -Open the `MigrationsDbContext` (`BookStoreMigrationsDbContext` for this example) and change as shown below: +Instead of directly changing the `MigrationsDbContext`, we should use the entity extension system of the ABP Framework. Find the `YourProjectNameEntityExtensions` class in the `.EntityFrameworkCore` project of your solution (`BookStoreEntityExtensions` for this example) and change it as shown below: ````csharp -protected override void OnModelCreating(ModelBuilder builder) +public static class MyProjectNameEntityExtensions { - base.OnModelCreating(builder); + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); - /* Include modules to your migration db context */ - - ... - - /* Configure customizations for entities from the modules included */ - - //CONFIGURE THE CUSTOM ROLE PROPERTIES - builder.Entity(b => + public static void Configure() { - b.ConfigureCustomRoleProperties(); - }); - - ... - - /* Configure your own tables/entities inside the ConfigureBookStore method */ - - builder.ConfigureBookStore(); + OneTimeRunner.Run(() => + { + ObjectExtensionManager.Instance + .MapEfCoreProperty( + "Title", + builder => { builder.HasMaxLength(64); } + ); + }); + } } ```` -Only added the following lines: +> Instead of hard-coded "Title" string, we suggest to use `nameof(AppRole.Title)` or use a constant string. -````csharp -builder.Entity(b => -{ - b.ConfigureCustomRoleProperties(); -}); -```` +`ObjectExtensionManager` is used to add properties to existing entities. Since `ObjectExtensionManager.Instance` is a static instance (singleton), we should call it once. `OneTimeRunner` is a simple utility class defined by the ABP Framework. + +See the [EF Core integration documentation](Entity-Framework-Core.md) for more about the entity extension system. -In this way, we re-used the extension method that is used to configure custom property mappings for the role. But, this time, did the same customization for the `IdentityRole` entity. +> We've repeated a similar database mapping code, like `HasMaxLength(128)`, in both classes. Now, you can add a new EF Core database migration using the standard `Add-Migration` command in the Package Manager Console (remember to select `.EntityFrameworkCore.DbMigrations` as the Default Project in the PMC and make sure that the `.Web` project is still the startup project): @@ -536,7 +504,7 @@ Instead of creating a new entity class to add a custom property, you can use the ###### Using the ExtraProperties -All entities derived from the `AggregateRoot ` class can store name-value pairs in their `ExtraProperties` property, which is a `Dictionary` serialized to JSON in the database table. So, you can add values to this dictionary and query again without changing the entity. +All entities derived from the `AggregateRoot ` class can store name-value pairs in their `ExtraProperties` property (because they implement the `IHasExtraProperties` interface), which is a `Dictionary` serialized to JSON in the database table. So, you can add values to this dictionary and query again without changing the entity. For example, you can store query the title Property inside an `IdentityRole` instead of creating a new entity. Example: @@ -553,16 +521,13 @@ public class IdentityRoleExtendingService : ITransientDependency public async Task GetTitleAsync(Guid id) { var role = await _identityRoleRepository.GetAsync(id); - return role.GetProperty("Title"); } public async Task SetTitleAsync(Guid id, string newTitle) { var role = await _identityRoleRepository.GetAsync(id); - role.SetProperty("Title", newTitle); - await _identityRoleRepository.UpdateAsync(role); } } @@ -575,12 +540,20 @@ In this way, you can easily attach any type of value to an entity of a depended * All the extra properties are stored as **a single JSON object** in the database. They are not stored as new table fields, as you may expect. Creating database table indexes and using SQL queries against these properties will be harder compared to simple table fields. * Property names are strings, so they are **not type safe**. It is recommended to define constants for these kind of properties to prevent typo errors. +###### Using the Entity Extensions System + +Entity extension system solves the main problem of the extra properties: It can store an extra property in a **standard table field** in the database. + +All you need to do is to use the `ObjectExtensionManager` to define the extra property as explained above, in the `AppRole` example. Then you can continue to use the same `GetProperty` and `SetProperty` methods defined above to get/set the related property on the entity, but this time stored as a separate field in the database. + ###### Creating a New Table Instead of creating a new entity and mapping to the same table, you can also create **your own table** to store your properties. You typically duplicate some values of the original entity. For example, you can add `Name` field to your own table which is a duplication of the `Name` field in the original table. In this case, you don't deal with migration problems, however you need to deal with the problems of data duplication. When the duplicated value changes, you should reflect the same change in your table. You can use local or distributed [event bus](Event-Bus.md) to subscribe to the change events for the original entity. This is the recommended way of depending on a microservice's data from another microservice, especially if they have separate physical databases (you can search on the web on data sharing on a microservice design, it is a wide topic to cover here). +> See the "[extending entities](Customizing-Application-Modules-Extending-Entities.md)" guide for more details on extending entities, including data duplication and synchronization tips. + #### Discussion of an Alternative Scenario: Every Module Manages Its Own Migration Path As mentioned before, `.EntityFrameworkCore.DbMigrations` merges all the database mappings of all the modules (plus your application's mappings) to create a unified migration path. diff --git a/docs/en/Entity-Framework-Core.md b/docs/en/Entity-Framework-Core.md index 788f3234ed..66f9d3c2f9 100644 --- a/docs/en/Entity-Framework-Core.md +++ b/docs/en/Entity-Framework-Core.md @@ -58,6 +58,53 @@ namespace MyCompany.MyProject } ```` +### About the EF Core Fluent Mapping + +The [application startup template](Startup-Templates/Application.md) has been configured to use the [EF Core fluent configuration API](https://docs.microsoft.com/en-us/ef/core/modeling/) to map your entities to your database tables. + +You can still use the **data annotation attributes** (like `[Required]`) on the properties of your entity while the ABP documentation generally follows the **fluent mapping API** approach. It is up to you. + +ABP Framework has some **base entity classes** and **conventions** (see the [entities document](Entities.md)) and it provides some useful **extension methods** to configure the properties inherited from the base entity classes. + +#### ConfigureByConvention Method + +`ConfigureByConvention()` is the main extension method that **configures all the base properties** and conventions for your entities. So, it is a **best practice** to call this method for all your entities, in your fluent mapping code. + +**Example**: Assume that you've a `Book` entity derived from `AggregateRoot` base class: + +````csharp +public class Book : AuditedAggregateRoot +{ + public string Name { get; set; } +} +```` + +You can override the `OnModelCreating` method in your `DbContext` and configure the mapping as shown below: + +````csharp +protected override void OnModelCreating(ModelBuilder builder) +{ + //Always call the base method + base.OnModelCreating(builder); + + builder.Entity(b => + { + b.ToTable("Books"); + + //Configure the base properties + b.ConfigureByConvention(); + + //Configure other properties (if you are using the fluent API) + b.Property(x => x.Name).IsRequired().HasMaxLength(128); + }); +} +```` + +* Calling `b.ConfigureByConvention()` is important here to properly **configure the base properties**. +* You can configure the `Name` property here or you can use the **data annotation attributes** (see the [EF Core document](https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties)). + +> While there are many extension methods to configure your base properties, `ConfigureByConvention()` internally calls them if necessary. So, it is enough to call it. + ### Configure the Connection String Selection If you have multiple databases in your application, you can configure the connection string name for your DbContext using the `[ConnectionStringName]` attribute. Example: @@ -225,7 +272,7 @@ public override async Task DeleteAsync( } ```` -### Access to the EF Core API +## Access to the EF Core API In most cases, you want to hide EF Core APIs behind a repository (this is the main purpose of the repository pattern). However, if you want to access the `DbContext` instance over the repository, you can use `GetDbContext()` or `GetDbSet()` extension methods. Example: @@ -251,9 +298,60 @@ public class BookService > Important: You must reference to the `Volo.Abp.EntityFrameworkCore` package from the project you want to access to the DbContext. This breaks encapsulation, but this is what you want in that case. -### Advanced Topics +## Extra Properties & Object Extension Manager + +Extra Properties system allows you to set/get dynamic properties to entities those implement the `IHasExtraProperties` interface. It is especially useful when you want to add custom properties to the entities defined in an [application module](Modules/Index.md), when you use the module as package reference. + +By default, all the extra properties of an entity are stored as a single `JSON` object in the database. -#### Set Default Repository Classes +Entity extension system allows you to to store desired extra properties in separate fields in the related database table. For more information about the extra properties & the entity extension system, see the following documents: + +* [Customizing the Application Modules: Extending Entities](Customizing-Application-Modules-Extending-Entities.md) +* [Entities](Entities.md) + +This section only explains the EF Core related usage of the `ObjectExtensionManager`. + +### ObjectExtensionManager.Instance + +`ObjectExtensionManager` implements the singleton pattern, so you need to use the static `ObjectExtensionManager.Instance` to perform all the operations. + +### MapEfCoreProperty + +`MapEfCoreProperty` is a shortcut extension method to define an extension property for an entity and map to the database. + +**Example**: Add `Title` property (database field) to the `IdentityRole` entity: + +````csharp +ObjectExtensionManager.Instance + .MapEfCoreProperty( + "Title", + builder => { builder.HasMaxLength(64); } + ); +```` + +If the related module has implemented this feature (by using the `ConfigureEfCoreEntity` explained below), then the new property is added to the model. Then you need to run the standard `Add-Migration` and `Update-Database` commands to update your database to add the new field. + +>`MapEfCoreProperty` method must be called before using the related `DbContext`. It is a static method. The best way is to use it in your application as earlier as possible. The application startup template has a `YourProjectNameEntityExtensions` class that is safe to use this method inside. + +### ConfigureEfCoreEntity + +If you are building a reusable module and want to allow application developers to add properties to your entities, you can use the `ConfigureEfCoreEntity` extension method in your entity mapping. However, there is a shortcut extension method `ConfigureObjectExtensions` that can be used while configuring the entity mapping: + +````csharp +builder.Entity(b => +{ + b.ConfigureObjectExtensions(); + //... +}); +```` + +> If you call `ConfigureByConvention()` extension method (like `b.ConfigureByConvention()` for this example), ABP Framework internally calls the `ConfigureObjectExtensions` method. It is a **best practice** to use the `ConfigureByConvention()` method since it also configures database mapping for base properties by convention. + +See the "*ConfigureByConvention Method*" section above for more information. + +## Advanced Topics + +### Set Default Repository Classes Default generic repositories are implemented by `EfCoreRepository` class by default. You can create your own implementation and use it for all the default repository implementations. @@ -299,7 +397,7 @@ context.Services.AddAbpDbContext(options => }); ``` -#### Set Base DbContext Class or Interface for Default Repositories +### Set Base DbContext Class or Interface for Default Repositories If your DbContext inherits from another DbContext or implements an interface, you can use that base class or interface as DbContext for default repositories. Example: @@ -331,7 +429,7 @@ public class BookRepository : EfCoreRepository, One advantage of using an interface for a DbContext is then it will be replaceable by another implementation. -#### Replace Other DbContextes +### Replace Other DbContextes Once you properly define and use an interface for DbContext, then any other implementation can replace it using the `ReplaceDbContext` option: @@ -344,3 +442,7 @@ context.Services.AddAbpDbContext(options => ```` In this example, `OtherDbContext` implements `IBookStoreDbContext`. This feature allows you to have multiple DbContext (one per module) on development, but single DbContext (implements all interfaces of all DbContexts) on runtime. + +## See Also + +* [Entities](Entities.md) \ No newline at end of file diff --git a/docs/en/Exception-Handling.md b/docs/en/Exception-Handling.md index d31a2703aa..6dc4ccbd9f 100644 --- a/docs/en/Exception-Handling.md +++ b/docs/en/Exception-Handling.md @@ -291,6 +291,26 @@ services.Configure(options => }); ```` +## Subscribing to the Exceptions + +It is possible to be informed when the ABP Framework **handles an exception**. It automatically **logs** all the exceptions to the standard [logger](Logging.md), but you may want to do more. + +In this case, create a class derived from the `ExceptionSubscriber` class in your application: + +````csharp +public class MyExceptionSubscriber : ExceptionSubscriber +{ + public override async Task HandleAsync(ExceptionNotificationContext context) + { + //TODO... + } +} +```` + +The `context` object contains necessary information about the exception occurred. + +> You can have multiple subscribers, each gets a copy of the exception. Exceptions thrown by your subscriber is ignored (but still logged). + ## Built-In Exceptions Some exception types are automatically thrown by the framework: diff --git a/docs/en/Getting-Started-With-Startup-Templates.md b/docs/en/Getting-Started-With-Startup-Templates.md new file mode 100644 index 0000000000..19442ec81e --- /dev/null +++ b/docs/en/Getting-Started-With-Startup-Templates.md @@ -0,0 +1,6 @@ +# Getting Started with the Startup Templates + +See the following tutorials to learn how to get started with the ABP Framework using the pre-built application startup templates: + +* [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started-AspNetCore-MVC-Template.md) +* [Getting Started with the Angular UI](Getting-Started-Angular-Template.md) \ No newline at end of file diff --git a/docs/en/Modules/Docs.md b/docs/en/Modules/Docs.md index db752ceb74..bbed28e8fe 100644 --- a/docs/en/Modules/Docs.md +++ b/docs/en/Modules/Docs.md @@ -134,7 +134,7 @@ An ABP module must declare `[DependsOn]` attribute if it has a dependency upon a { public override void ConfigureServices(ServiceConfigurationContext context) { - Configure(options => + Configure(options => { options.DefinitionProviders.Add(); }); diff --git a/docs/en/Object-Extensions.md b/docs/en/Object-Extensions.md new file mode 100644 index 0000000000..fad3ff2b0c --- /dev/null +++ b/docs/en/Object-Extensions.md @@ -0,0 +1,3 @@ +# Object Extensions + +TODO \ No newline at end of file diff --git a/docs/en/Startup-Templates/Application.md b/docs/en/Startup-Templates/Application.md index 3a5c6c3bb2..8298c01f1b 100644 --- a/docs/en/Startup-Templates/Application.md +++ b/docs/en/Startup-Templates/Application.md @@ -2,11 +2,12 @@ ## Introduction -This template provides a layered application structure based on the [Domain Driven Design](../Domain-Driven-Design.md) (DDD) practices. This document explains the solution structure and projects in details. If you want to start quickly, follow the guides below: +This template provides a layered application structure based on the [Domain Driven Design](../Domain-Driven-Design.md) (DDD) practices. -* See [Getting Started With the ASP.NET Core MVC Template](../Getting-Started-AspNetCore-MVC-Template.md) to create a new solution and run it for this template (uses MVC as the UI framework and Entity Framework Core as the database provider). -* See the [ASP.NET Core MVC Application Development Tutorial](../Tutorials/Part-1.md?UI=MVC) to learn how to develop applications using this template (uses MVC as the UI framework and Entity Framework Core as the database provider). -* See the [Angular Application Development Tutorial](../Tutorials/Part-1.md?UI=NG) to learn how to develop applications using this template (uses Angular as the UI framework and MongoDB as the database provider). +This document explains **the solution structure** and projects in details. If you want to start quickly, follow the guides below: + +* [The getting started document](../Getting-Started-With-Startup-Templates.md) explains how to create a new application in a few minutes. +* [The application development tutorial](../Tutorials/Part-1) explains step by step application development. ## How to Start With? @@ -123,6 +124,8 @@ Notice that the migration `DbContext` is only used for database migrations and * * Depends on the `.EntityFrameworkCore` project since it re-uses the configuration defined for the `DbContext` of the application. > This project is available only if you are using EF Core as the database provider. +> +> See the [Entity Framework Core Migrations Guide](../Entity-Framework-Core-Migrations.md) to understand this project in details. #### .DbMigrator Project @@ -269,5 +272,5 @@ The files under the `angular/src/environments` folder has the essential configur ## What's Next? -- See [Getting Started With the ASP.NET Core MVC Template](../Getting-Started-AspNetCore-MVC-Template.md) to create a new solution and run it for this template. -- See the [ASP.NET Core MVC Tutorial](../Tutorials/Part-1.md) to learn how to develop applications using this template. +- [The getting started document](../Getting-Started-With-Startup-Templates.md) explains how to create a new application in a few minutes. +- [The application development tutorial](../Tutorials/Part-1) explains step by step application development. diff --git a/docs/en/UI/Angular/Component-Replacement.md b/docs/en/UI/Angular/Component-Replacement.md index b17c61ae73..d718b46117 100644 --- a/docs/en/UI/Angular/Component-Replacement.md +++ b/docs/en/UI/Angular/Component-Replacement.md @@ -1,10 +1,10 @@ -# Component Replacement +## Component Replacement You can replace some ABP components with your custom components. The reason that you **can replace** but **cannot customize** default ABP components is disabling or changing a part of that component can cause problems. So we named those components as _Replaceable Components_. -## How to Replace a Component +### How to Replace a Component Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`. @@ -29,7 +29,54 @@ export class AppComponent { ![Example Usage](./images/component-replacement.gif) -## Available Replaceable Components + +### How to Replace a Layout + +Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced with the same way. + +> A layout component template should contain `` element. + +The below example describes how to replace the `ApplicationLayoutComponent`: + +Run the following command to generate a layout in `angular` folder: + +```bash +yarn ng generate component shared/my-application-layout --export --entryComponent + +# You don't need the --entryComponent option in Angular 9 +``` + +Add the following code in your layout template (`my-layout.component.html`) where you want the page to be loaded. + +```html + +``` + +Open the `app.component.ts` and add the below content: + +```js +import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent +import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent +import { Store } from '@ngxs/store'; // imported Store +//... +export class AppComponent { + constructor(..., private store: Store) {} // injected Store + + ngOnInit() { + // added below content + this.store.dispatch( + new AddReplaceableComponent({ + component: MyApplicationLayoutComponent, + key: 'Theme.ApplicationLayoutComponent', + }), + ); + + //... + } +} +``` + +### Available Replaceable Components | Component key | Description | | -------------------------------------------------- | --------------------------------------------- | diff --git a/docs/en/UI/Angular/Config-State.md b/docs/en/UI/Angular/Config-State.md index 72c8e6c6fd..7fc202982c 100644 --- a/docs/en/UI/Angular/Config-State.md +++ b/docs/en/UI/Angular/Config-State.md @@ -288,3 +288,7 @@ Note that **you do not have to call this method at application initiation**, bec #### Environment Properties Please refer to `Config.Environment` type for all the properties you can pass to `dispatchSetEnvironment` as parameter. It can be found in the [config.ts file](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L13). + +## What's Next? + +* [Component Replacement](./Component-Replacement.md) \ No newline at end of file diff --git a/docs/en/UI/Angular/Content-Security-Strategy.md b/docs/en/UI/Angular/Content-Security-Strategy.md new file mode 100644 index 0000000000..21d794a1af --- /dev/null +++ b/docs/en/UI/Angular/Content-Security-Strategy.md @@ -0,0 +1,56 @@ +# ContentSecurityStrategy + +`ContentSecurityStrategy` is an abstract class exposed by @abp/ng.core package. It helps you mark inline scripts or styles as safe in terms of [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy). + + + + +## API + + +### constructor(public nonce?: string) + +- `nonce` enables whitelisting inline script or styles in order to avoid using `unsafe-inline` in [script-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#Unsafe_inline_script) and [style-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src#Unsafe_inline_styles) directives. + + +### applyCSP(element: HTMLScriptElement | HTMLStyleElement): void + +This method maps the aforementioned properties to the given `element`. + + + + +## LooseContentSecurityPolicy + +`LooseContentSecurityPolicy` is a class that extends `ContentSecurityStrategy`. It requires `nonce` and marks given ` +```` + +在你自己的代码中更改 `UA-xxxxxx-1` . + +然后你可以在你模块的 `ConfigureServices` 方法将这个组件添加到任何的钩子点: + +````csharp +Configure(options => +{ + options.Add( + LayoutHooks.Head.Last, //The hook name + typeof(GoogleAnalyticsViewComponent) //The component to add + ); +}); +```` + +现在谷歌统计代码将在页面的 `head` 所为最后一项插入. 你(或你在使用的模块)可以将多个项添加到相同的钩子,它们都会添加到布局. + +在上面我们添加 `GoogleAnalyticsViewComponent` 到所有的布局,你可能只想添加到指定的布局: + +````csharp +Configure(options => +{ + options.Add( + LayoutHooks.Head.Last, + typeof(GoogleAnalyticsViewComponent), + layout: StandardLayouts.Application //Set the layout to add + ); +}); +```` + +参阅下面的布局部分,以了解有关布局系统的更多信息. + +### 布局 + +布局系统允许主题定义标准,命名布局并且允许任何页面选择使用合适的布局. 有三种预定义的布局: + +* "**Application**": 应用程序的主要(和默认)布局. 它通常包含页眉,菜单(侧栏),页脚,工具栏等. +* "**Account**": 登录,注册和其他类似页面使用此布局. 默认它用于 `/Pages/Account` 文件夹下的页面. +* "**Empty**": 空的最小的布局. + +这些名称在 `StandardLayouts` 类定义为常量. 这是标准的布局名称,所有的主题开箱即用的实现. 你也可以创建自己的布局. + +#### 布局位置 + +你可以在[这里](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts)找到基本主题的布局文件. 你可以将它们作用构建自己的布局的参考,也可以在必要时覆盖它们. + +#### ITheme + +ABP框架使用 `ITheme` 服务通过局部名称获取布局位置. 你可以替换此服务动态的选择布局位置. + +#### IThemeManager + +`IThemeManager` 用于获取当前主题,并得到了布局路径. 任何页面可以都决定自己的布局. 例: + +````html +@using Volo.Abp.AspNetCore.Mvc.UI.Theming +@inject IThemeManager ThemeManager +@{ + Layout = ThemeManager.CurrentTheme.GetLayout(StandardLayouts.Empty); +} +```` + +此页面将使用空白布局. 它使用 `ThemeManager.CurrentTheme.GetEmptyLayout()` 扩展方法. + +如果你设置特定目录下所有页面的布局,可以在该文件夹下的 `_ViewStart.cshtml` 文件编写以上代码. diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Alerts.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Alerts.md new file mode 100644 index 0000000000..fa70c1c9b0 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Alerts.md @@ -0,0 +1,80 @@ +# Alerts + +## 介绍 + +`abp-alert` 是创建alert的主要元素. + +基本用法: + +````xml + + A simple primary alert—check it out! + +```` + +## Demo + +参阅[alerts demo页面](https://bootstrap-taghelpers.abp.io/Components/Alerts)查看示例. + +## Attributes + +### alert-type + +值做为alert的Type,应为以下值之一: + +* `Default` (默认值) +* `Primary` +* `Secondary` +* `Success` +* `Danger` +* `Warning` +* `Info` +* `Light` +* `Dark` + +示例: + +````xml + + A simple warning alert—check it out! + +```` + +### alert-link + +alert的链接. + +示例: + +````xml + + A simple danger alert with an example link. Give it a click if you like. + +```` + +### dismissible + +使alert可被忽略: + +示例: + +````xml + + Holy guacamole! You should check in on some of those fields below. + +```` + +### Additional content + +`abp-alert`还可以包含其他HTML元素,例如标题,段落和分隔符. + +示例: + +````xml + +

Well done!

+

Aww yeah, you successfully read this important alert message. This example text is going to run a bit longer so that you can see how spacing within an alert works with this kind of content.

+
+

Whenever you need to, be sure to use margin utilities to keep things nice and tidy.

+
+```` diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Buttons.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Buttons.md index 76307ad820..f628f17ef4 100644 --- a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Buttons.md +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Buttons.md @@ -12,7 +12,7 @@ ## Demo -参阅 [按钮Demo页面](https://bootstrap-taghelpers.abp.io/Components/Buttons) 查看示例. +参阅[按钮Demo页面](https://bootstrap-taghelpers.abp.io/Components/Buttons)查看示例. ## Attributes @@ -20,7 +20,7 @@ 指定按钮的主样式/类型. 应为以下值之一: -* `Default` (default value) +* `Default` (默认值) * `Primary` * `Secondary` * `Success` diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Cards.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Cards.md new file mode 100644 index 0000000000..4cb75ea3b0 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Cards.md @@ -0,0 +1,180 @@ +# 卡片 + +## 介绍 + +`abp-card` 是从bootstrap card元素派生的内容容器. + +基本用法: + +````xml + + + + Card Title + Some quick example text to build on the card title and make up the bulk of the card's content. + Go somewhere + + +```` + +##### 使用标题,文本和链接: + +`abp-card` 可以使用以下标签 + +* `abp-card-title` +* `abp-card-subtitle` +* `a abp-card-link` + +示例: + +````xml + + + Card title + Card subtitle + Some quick example text to build on the card title and make up the bulk of the card's content. + Card link + Another link + + +```` + +##### 使用列表组: + +* `abp-list-group flush="true"` : `flush` 属性渲染到 bootstrap `list-group-flush` 的 calss 中,该类用于删除边界和圆角以使列表组项在父容器中并排显示. +* `abp-list-group-item` + +示例: + +````xml + + + + Card Title + Some quick example text to build on the card title and make up the bulk of the card's content. + + + Cras justo odio + Dapibus ac facilisis in + Vestibulum at eros + + + Card link + Another link + + +```` + +##### 使用页眉,页脚和块引用: + +* `abp-card-header` +* `abp-card-footer` +* `abp-blockquote` + +示例: + +```xml + + Featured + + Special title treatment + With supporting text below as a natural lead-in to additional content. + Go somewhere + + +``` + +引用示例: + +```xml + + Quote + + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+
Someone famous in Source Title
+
+
+
+``` + +页脚示例: + +```xml + + Featured + + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante.

+
Someone famous in Source Title
+
+
+ 2 days ago +
+``` + +## Demo + +参阅[卡片demo页面](https://bootstrap-taghelpers.abp.io/Components/Cards)查看示例. + +## abp-card Attributes + +- **background:** 值指定卡片背景的颜色. +- **text-color**: 值指定卡片内文本的颜色. +- **border:** 值指定卡片边框的颜色. + +应为以下值之一: + +* `Default` (默认值) +* `Primary` +* `Secondary` +* `Success` +* `Danger` +* `Warning` +* `Info` +* `Light` +* `Dark` + +示例: + +````xml + +```` + +### sizing + +卡片的默认值为100%,可以使用自定义CSS,栅格类,栅格Sass mixins或[utilities](https://getbootstrap.com/docs/4.0/utilities/sizing/)进行更改. + +````xml + +```` + +### card-deck 和 card-columns + +`abp-card` 可以在 `card-deck` 或 `card-columns` 里使用. + +````xml +
+ + First Deck + + Ace + Here is the content for Ace. + + + + Second Deck + + Beta + Beta content. + + + + Third Deck + + Epsilon + Content for Epsilon. + + +
+```` diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Collapse.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Collapse.md new file mode 100644 index 0000000000..65327de438 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Collapse.md @@ -0,0 +1,90 @@ +# Collapse + +## 介绍 + +`abp-collapse-body` 是显示和隐藏内容的主要容器. `abp-collapse-id` 用于显示和隐藏内容容器. 可以通过 `abp-button` 和 `a` 标签触发. + +基本用法: + +````xml + + Link with href + + + Anim pariatur wolf moon tempor,,, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. + +```` + +## Demo + +参阅[collapse demo 页面](https://bootstrap-taghelpers.abp.io/Components/Collapse)查看示例. + +## Attributes + +### show + +指定折叠主体初始化时是折叠还是展开. 应为以下值之一: + +* `false` (默认值) +* `true` + +### multi + +指定 `abp-collapse-body` 是否可以通过显示/隐藏多个折叠体的元素显示或隐藏. 此属性做为 "multi-collapse" class 添加到 `abp-collapse-body`. 应为以下值之一: + +* `false` (默认值) +* `true` + +示例: + +````xml + Toggle first element + + + + + + + Curabitur porta porttitor libero eu luctus. Praesent ultrices mattis commodo. Integer sodales massa risus, in molestie enim sagittis blandit + + + + + Anim pariatur wolf moon tempor,,, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. + + + +```` + +## 手风琴示例 + +`abp-accordion` 是手风琴项的主容器. + +基本用法: + +````xml + + + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry rtat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. + + + Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. + + + Anim pariatur wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS. + + +```` + +## Attributes + +### active + +指定手风琴项目在初始化时是显示还是隐藏. 应为以下值之一: + +* `false` (默认值) +* `true` + +### title + +指定手风琴项的标题. 应为字符串类型的值. diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Dropdowns.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Dropdowns.md new file mode 100644 index 0000000000..971dce7a1c --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Dropdowns.md @@ -0,0 +1,93 @@ +# 下拉菜单 + +## 介绍 + +`abp-dropdown` 是下拉菜单的主要容器. + +基本用法: + +````xml + + + + Action + Another action + Something else here + + +```` + +## Demo + +参阅 [下拉菜单demo页面](https://bootstrap-taghelpers.abp.io/Components/Dropdowns)查看示例. + +## Attributes + +### direction + +指定下拉菜单的方向. 应为以下值之一: + +* `Down` (默认值) +* `Up` +* `Right` +* `Left` + +### dropdown-style + +指定 `abp-dropdown-button` 是否具有用于拆分的拆分图标. 应为以下值之一: + +* `Single` (默认值) +* `Split` + +## Menu items + +`abp-dropdown-menu` 下拉菜单项的主要容器. + +基本用法: + +````xml + + + + Dropdown Header + Action + Active action + Disabled action + + Dropdown Item Text + Something else here + + +```` + +## Attributes + +### align + +指定 `abp-dropdown-menu` 在哪个方向对齐. 应为以下值之一: + +* `Left` (默认值) +* `Right` + +### Additional content + +`abp-dropdown-menu` 也可以包含其他HTML元素,例如标题,段落,分隔符或form元素. + +示例: + +````xml + + + +
+ + + + + + + New around here? Sign up + Forgot password? +
+
+```` diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Grids.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Grids.md new file mode 100644 index 0000000000..0605e893f0 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Grids.md @@ -0,0 +1,273 @@ +# 栅格 + +## 介绍 + +ABP标签助手基于bootstrap栅格系统. + +## Demo + +参阅[网络demo页面](https://bootstrap-taghelpers.abp.io/Components/Grids)查看示例. + +### Sizing + +**等宽:** 创建等宽的列. + +示例: + +````xml + + + 1 of 2 + 2 of 2 + + + 1 of 3 + 2 of 3 + 3 of 3 + + +```` + +**列中断:** `abp-column-breaker` 用于中断当前行的自动放置宽度,然后在新行中开始. + +示例: + +````xml + + + column + column + + column + column + + +```` + +**设置列宽度:** size属性用于设置特定列的宽度. + +示例: + +```xml + + + 1 of 3 + 2 of 3 (wider) + 3 of 3 + + + 1 of 3 + 2 of 3 (wider) + 3 of 3 + + +``` + +**可变宽度内容:** 根据内容自动调整列的大小. + +```xml + + + 1 of 3 + Contrary to popular belief, Lorem Ipsum is not simply random text. + 3 of 3 + + + 1 of 3 + Variable width content + 3 of 3 + + +``` + +### 响应类 + +响应式类可以在abp标签中强类型使用. + +```xml + + col-sm-8 + col-sm-4 + + + col-sm + col-sm + col-sm + col-sm + + + + .col-12 .col-md-8 + .col-6 .col-md-4 + + + + + .col-6 .col-md-4 + .col-6 .col-md-4 + .col-6 .col-md-4 + + + + + .col-6 + .col-6 + +``` + +### 对齐 + +可以使用强类型的abp标签在垂直和水平方向上进行列对齐. + +**垂直对齐**: `v-align` 属性值用于对垂直齐列. + +示例: + +```xml + + + column + column + column + + + column + column + column + + + column + column + column + + +``` + +**水平对齐**: `h-align` 属性值用于对水平齐列. + +示例: + +```xml + + + One of two columns + One of two columns + + + One of two columns + One of two columns + + + One of two columns + One of two columns + + + One of two columns + One of two columns + + + One of two columns + One of two columns + + +``` + +**无间隙**: 可以使用 `gutters="false"` 删除预定义栅格类中列之间的栅格线. 这会从 `abp-row` 中消除负边距,并从所有直接子列中消除水平边距. + +示例: + +```xml + + One of two columns + One of two columns + +``` + +**列包装**: 如果在一行中放置超过12列,则将每组额外的列作为一个单元包装到新行上. + +示例: + +```xml + + .col-9 + .col-4
Since 9 + 4 = 13 > 12, this 4-column-wide div gets wrapped onto a new line as one contiguous unit.
+ .col-6
Subsequent columns continue along the new line.s
+
+``` + +### 重新排序 + +**Order类**: `order` 属性用于控制内容的视觉顺序. + +示例: + +```xml + + + First, but Last + Second, but unordered + Third, but Second + + +``` + +**偏移列**: `offset` 属性用于设置栅格列的偏移量. + +示例: + +```xml + + + .col-md-4 + .col-md-4 .offset-md-4 + + + .col-md-3 .offset-md-3 + .col-md-3 .offset-md-3 + + + .col-md-6 .offset-md-3 + + + .col-sm-5 .col-md-6 + .col-sm-5 .offset-sm-2 .col-md-6 .offset-md-0 + + + col-sm-6 .col-md-5 .col-lg-6 + .col-sm-6 .col-md-5 .offset-md-2 .col-lg-6 .offset-lg-0 + + +``` + +## abp-row Attributes + +- **v-align:** 指定包含列的垂直位置. 应为以下值之一: + * `Default` (默认值) + * `Start` + * `Center` + * `End` + +- **h-align**: 指定包含列的水平位置. 应为以下值之一: + * `Default` (默认值) + * `Start` + * `Center` + * `Around` + * `Between` + * `End` +- **gutter**: 指定是否将从所有子列中删除负边距和水平填充. 如果未设置,默认为 `true`. 应为以下值之一: + * `true` + * `false` + +## abp-column Attributes + +- **size:** 指定列的宽度: `_`, `Undefined`, `_1`..`_12`, `Auto`. 或者可以与预定义值一起使用,例如: + - `size-sm` + - `size-md` + - `size-lg` + - `size-xl` +- **order**: 指定列的顺序: `Undefined`, `_1`..`_12`, `First` 和 `Last`. +- **offset:** 指定列的偏移量: `_`, `Undefined`, `_1`..`_12`, `Auto`. 或者可以与预定义值一起使用,例如: + - `offset-sm` + - `offset-md` + - `offset-lg` + - `offset-xl` \ No newline at end of file diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Index.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Index.md index acbb9e872b..20d1119969 100644 --- a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Index.md +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Index.md @@ -13,6 +13,18 @@ ABP框架还向标准bootstrap组件添加了一些**实用的功能**. 这里是ABP框架包装的组件列表: * [Buttons](Buttons.md) +* [Cards](Cards.md) +* [Alerts](Alerts.md) +* [Tabs](Tabs.md) +* [Grids](Grids.md) +* [Modals](Modals.md) +* [Collapse](Collapse.md) +* [Dropdowns](Dropdowns.md) +* [List Groups](List-Groups.md) +* [Paginator](Paginator.md) +* [Popovers](Popovers.md) +* [Progress Bars](Progress-Bars.md) +* [Tooltips](Tooltips.md) * ... > 在为所有的标签助手完成文档之前,你可以访问 https://bootstrap-taghelpers.abp.io/ 查看在线示例. diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/List-Groups.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/List-Groups.md new file mode 100644 index 0000000000..411a8a3b8a --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/List-Groups.md @@ -0,0 +1,76 @@ +# 列表组 + +## 介绍 + +`abp-list-group` 是列表组内容的主要容器. + +基本用法: + +````xml + + Cras justo odio + Dapibus ac facilisis in + Morbi leo risus + Vestibulum at eros + +```` + +## Demo + +参阅[列表组demo页面](https://bootstrap-taghelpers.abp.io/Components/ListGroups)查看示例. + +## Attributes + +### flush + +指定 `abp-list-group` 项可删除某些边框和圆角,以在父容器中无边框呈现列表组项. 应为以下值之一: + +* `false` (默认值) +* `true` + +### active + +指定 `abp-list-group-item` 是否处于active. 应为以下值之一: + +* `false` (默认值) +* `true` + +### disabled + +指定 `abp-list-group-item` 是否被禁用. 应为以下值之一: + +* `false` (默认值) +* `true` + +### href + +指定 `abp-list-group-item` 是否含有链接. 应该是一个字符串链接值. + +### type + +指定 `abp-list-group-item` 具有状态背景和颜色的样式类. 应为以下值之一: + +* `Default` (默认值) +* `Primary` +* `Secondary` +* `Success` +* `Danger` +* `Warning` +* `Info` +* `Light` +* `Dark` +* `Link` + +### Additional content + +`abp-list-group-item` 还可以包含其他HTML元素(例如span). + +示例: + +````xml + + Cras justo odio 14 + Dapibus ac facilisis in 2 + Morbi leo risus 1 + +```` diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Modals.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Modals.md new file mode 100644 index 0000000000..28b92254bd --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Modals.md @@ -0,0 +1,79 @@ +# 模态框 + +## 介绍 + +`abp-modal` 是创建模态框的主要元素. + +基本用法: + +````xml +Launch modal + + + + + Woohoo, you're reading this text in a modal! + + + +```` + +## Demo + +参阅[模态框demo页面](https://bootstrap-taghelpers.abp.io/Components/Modals)查看示例. + +## Attributes + +### centered + +指定模态框的位置. 应为以下值之一: + +* `false` (默认值) +* `true` + +### size + +指定模态框的大小. 应为以下值之一: + +* `Default` (默认值) +* `Small` +* `Large` +* `ExtraLarge` + +### static + +指定模态框是否是静态的. 应为以下值之一: + +* `false` (默认值) +* `true` + +### Additional content + +`abp-modal-footer` 可以有多个带有对齐选项的按钮. + +添加 `@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal` 到你的页面. + +示例: + +````xml +Launch modal + + + + + Woohoo, you're reading this text in a modal! + + + +```` + +### button-alignment + +指定模态页脚按钮的位置. 应为以下值之一: + +* `Default` (默认值) +* `Start` +* `Center` +* `Around` +* `Between` +* `End` \ No newline at end of file diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Paginator.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Paginator.md new file mode 100644 index 0000000000..7a9b3de126 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Paginator.md @@ -0,0 +1,55 @@ +# 分页器 + +## 介绍 + +`abp-paginator` 是分页器的abp标签. 需要 `Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination.PagerModel` 类型的模型. + +基本用法: + +````xml + +```` + +模型: + +````csharp +using Microsoft.AspNetCore.Mvc.RazorPages; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination; + +namespace Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo.Pages.Components +{ + public class PaginatorModel : PageModel + { + public PagerModel PagerModel { get; set; } + + public void OnGet(int currentPage, string sort) + { + PagerModel = new PagerModel(100, 10, currentPage, 10, "Paginator", sort); + } + } +} +```` + +## Demo + +参阅[分页器demo页面](https://bootstrap-taghelpers.abp.io/Components/Paginator)查看示例. + +## Attributes + +### model + +`Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Pagination.PagerModel` 类型模型可以用以下数据初始化: + +* `totalCount` +* `shownItemsCount` +* `currentPage` +* `pageSize` +* `pageUrl` +* `sort` (默认值为null) + +### show-info + +指定是否显示开始,结束和总记录的其他信息. 应为以下值之一: + +* `false` (默认值) +* `true` diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Progress-Bars.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Progress-Bars.md new file mode 100644 index 0000000000..e77580cd99 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Progress-Bars.md @@ -0,0 +1,68 @@ +# 进度条 + +## 介绍 + +`abp-progress-bar` 是进度条状态的abp标签. + +基本用法: + +````xml + + + %25 + + + + %50 + + + + %10 + + +```` + +## Demo + +参阅[进度条demo页面](https://bootstrap-taghelpers.abp.io/Components/Progress-Bars)查看示例. + +## Attributes + +### value + +指定当前进度条的进度. + +### type + +指定进度条的背景颜色. 应为下列值之一: + +* `Default` (默认值) +* `Secondary` +* `Success` +* `Danger` +* `Warning` +* `Info` +* `Light` +* `Dark` + +### min-value + +进度条的最小值. 默认值是0。 + +### max-value + +进度条的最大值. 默认值是100. + +### strip + +指定进度条的背景样式是否被去除. 应为以下值之一: + +* `false` (默认值) +* `true` + +### animation + +指定进度条的背景样式是否为动画. 应为以下值之一: + +* `false` (默认值) +* `true` \ No newline at end of file diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Tabs.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Tabs.md new file mode 100644 index 0000000000..3aacbb5e0d --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Tabs.md @@ -0,0 +1,91 @@ +# 标签页 + +## 介绍 + +`abp-tab` 是从引导标签元素派生的基本标签导航内容容器. + +基本用法: + +````xml + + + Content_Home + + + + Content_Profile + + + + Content_1_Content + + + Content_2_Content + + + +```` + +## Demo + +参阅[标签页demo页面](https://bootstrap-taghelpers.abp.io/Components/Tabs)查看示例. + +## abp-tab Attributes + +- **title**: 设置标签页菜单文字. +- **name:** 设置生成元素的"id"属性. 默认值为Guid. 除非使用Jquery更改或修改了选项卡,否则不需要. +- **active**: 设置active标签页. + +示例: + +````xml + + + Content_Home + + + Content_Profile + + + Content_Contact + + +```` + +### Pills + +示例: + +````xml + + + Content_Home + + + Content_Profile + + + Content_Contact + + +```` + +### Vertical + +**vertical-header-size**: 设置标签标题的列宽. + +示例: + +````xml + + + Content_Home + + + Content_Profile + + + Content_Contact + + +```` diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Tooltips.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Tooltips.md new file mode 100644 index 0000000000..0f404911b7 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Tooltips.md @@ -0,0 +1,33 @@ +# 提示工具 + +## 介绍 + +`abp-tooltip` 是提示工具的abp标签. + +基本用法: + +````xml + + Tooltip Default + + + + Tooltip on top + + + + Tooltip on right + + + + Tooltip on bottom + + + + Disabled button Tooltip + +```` + +## Demo + +参阅[提示工具demo页面](https://bootstrap-taghelpers.abp.io/Components/Tooltips)查看示例. diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/fa-address-card.png b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/fa-address-card.png deleted file mode 100644 index a3b2c815b8..0000000000 Binary files a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/fa-address-card.png and /dev/null differ diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 3837d2547f..c1aa2be23d 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -5,6 +5,7 @@ "items": [ { "text": "从启动模板开始", + "path": "Getting-Started-With-Startup-Templates.md", "items": [ { "text": "ASP.NET Core MVC 模板", @@ -282,6 +283,10 @@ "text": "仪表板和小部件(Widget)系统", "path": "UI/AspNetCore/Widgets.md" }, + { + "text": "自定义/扩展UI", + "path": "UI/AspNetCore/Customization-User-Interface.md" + }, { "text": "主题化", "path": "UI/AspNetCore/Theming.md" diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 88c8385b63..3db0998855 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -273,7 +273,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo", "test\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo.csproj", "{0C498CF2-D052-4BF7-AD35-509A90F69707}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Http.Client.IdentityModel.Web.Tests", "test\Volo.Abp.Http.Client.IdentityModel.Web.Tests\Volo.Abp.Http.Client.IdentityModel.Web.Tests.csproj", "{E1963439-2BE5-4DB5-8438-2A9A792A1ADA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Client.IdentityModel.Web.Tests", "test\Volo.Abp.Http.Client.IdentityModel.Web.Tests\Volo.Abp.Http.Client.IdentityModel.Web.Tests.csproj", "{E1963439-2BE5-4DB5-8438-2A9A792A1ADA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.ObjectExtending", "src\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj", "{D1815C77-16D6-4F99-8814-69065CD89FB3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.ObjectExtending.Tests", "test\Volo.Abp.ObjectExtending.Tests\Volo.Abp.ObjectExtending.Tests.csproj", "{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -817,6 +821,14 @@ Global {E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU {E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU {E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Release|Any CPU.Build.0 = Release|Any CPU + {D1815C77-16D6-4F99-8814-69065CD89FB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1815C77-16D6-4F99-8814-69065CD89FB3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1815C77-16D6-4F99-8814-69065CD89FB3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1815C77-16D6-4F99-8814-69065CD89FB3}.Release|Any CPU.Build.0 = Release|Any CPU + {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -956,6 +968,8 @@ Global {29E42ADB-85F8-44AE-A9B0-078F84C1B866} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {0C498CF2-D052-4BF7-AD35-509A90F69707} = {447C8A77-E5F0-4538-8687-7383196D04EA} {E1963439-2BE5-4DB5-8438-2A9A792A1ADA} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {D1815C77-16D6-4F99-8814-69065CD89FB3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Controllers/ErrorController.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Controllers/ErrorController.cs index 1fb4c12cf2..9682ec838e 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Controllers/ErrorController.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared/Controllers/ErrorController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Localization.Resources.AbpUi; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Mvc; @@ -7,6 +8,7 @@ using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.ExceptionHandling; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Views.Error; +using Volo.Abp.ExceptionHandling; namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Controllers { @@ -16,20 +18,23 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Controllers private readonly IHttpExceptionStatusCodeFinder _statusCodeFinder; private readonly IStringLocalizer _localizer; private readonly AbpErrorPageOptions _abpErrorPageOptions; + private readonly IExceptionNotifier _exceptionNotifier; public ErrorController( IExceptionToErrorInfoConverter exceptionToErrorInfoConverter, IHttpExceptionStatusCodeFinder httpExceptionStatusCodeFinder, IOptions abpErrorPageOptions, - IStringLocalizer localizer) + IStringLocalizer localizer, + IExceptionNotifier exceptionNotifier) { _errorInfoConverter = exceptionToErrorInfoConverter; _statusCodeFinder = httpExceptionStatusCodeFinder; _localizer = localizer; + _exceptionNotifier = exceptionNotifier; _abpErrorPageOptions = abpErrorPageOptions.Value; } - public IActionResult Index(int httpStatusCode) + public async Task Index(int httpStatusCode) { var exHandlerFeature = HttpContext.Features.Get(); @@ -37,6 +42,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Controllers ? exHandlerFeature.Error : new Exception(_localizer["UnhandledException"]); + await _exceptionNotifier.NotifyAsync(new ExceptionNotificationContext(exception)); + var errorInfo = _errorInfoConverter.Convert(exception); if (httpStatusCode == 0) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs index 28c1c18fda..1ce177ba44 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/AbpExceptionFilter.cs @@ -1,18 +1,21 @@ using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.AspNetCore.ExceptionHandling; using Volo.Abp.DependencyInjection; +using Volo.Abp.ExceptionHandling; using Volo.Abp.Http; using Volo.Abp.Json; namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling { - public class AbpExceptionFilter : IExceptionFilter, ITransientDependency + public class AbpExceptionFilter : IAsyncExceptionFilter, ITransientDependency { public ILogger Logger { get; set; } @@ -32,14 +35,14 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling Logger = NullLogger.Instance; } - public virtual void OnException(ExceptionContext context) + public async Task OnExceptionAsync(ExceptionContext context) { if (!ShouldHandleException(context)) { return; } - HandleAndWrapException(context); + await HandleAndWrapException(context); } protected virtual bool ShouldHandleException(ExceptionContext context) @@ -65,7 +68,7 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling return false; } - protected virtual void HandleAndWrapException(ExceptionContext context) + protected virtual async Task HandleAndWrapException(ExceptionContext context) { //TODO: Trigger an AbpExceptionHandled event or something like that. @@ -82,6 +85,13 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling Logger.LogWithLevel(logLevel, _jsonSerializer.Serialize(remoteServiceErrorInfo, indented: true)); Logger.LogException(context.Exception, logLevel); + await context.HttpContext + .RequestServices + .GetRequiredService() + .NotifyAsync( + new ExceptionNotificationContext(context.Exception) + ); + context.Exception = null; //Handled! } } diff --git a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs index cdd921dae4..9a7a8f1676 100644 --- a/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs +++ b/framework/src/Volo.Abp.AspNetCore/Volo/Abp/AspNetCore/ExceptionHandling/AbpExceptionHandlingMiddleware.cs @@ -7,6 +7,7 @@ using Microsoft.Net.Http.Headers; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.AspNetCore.Uow; using Volo.Abp.DependencyInjection; +using Volo.Abp.ExceptionHandling; using Volo.Abp.Http; using Volo.Abp.Json; @@ -73,6 +74,13 @@ namespace Volo.Abp.AspNetCore.ExceptionHandling ) ) ); + + await httpContext + .RequestServices + .GetRequiredService() + .NotifyAsync( + new ExceptionNotificationContext(exception) + ); } private Task ClearCacheHeaders(object state) diff --git a/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs b/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs new file mode 100644 index 0000000000..664e35e9b0 --- /dev/null +++ b/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; + +namespace AutoMapper +{ + public static class AbpAutoMapperExtensibleDtoExtensions + { + public static IMappingExpression MapExtraProperties( + this IMappingExpression mappingExpression, + MappingPropertyDefinitionChecks? definitionChecks = null) + where TDestination : IHasExtraProperties + where TSource : IHasExtraProperties + { + return mappingExpression + .ForMember( + x => x.ExtraProperties, + y => y.MapFrom( + (source, destination, extraProps) => + { + var result = extraProps.IsNullOrEmpty() + ? new Dictionary() + : new Dictionary(extraProps); + + HasExtraPropertiesObjectExtendingExtensions + .MapExtraPropertiesTo( + source.ExtraProperties, + result, + definitionChecks + ); + + return result; + }) + ); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj index e6146fedde..327f2078d1 100644 --- a/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj +++ b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj @@ -15,6 +15,7 @@ + diff --git a/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs b/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs index ba3ef2ada8..1590e879b1 100644 --- a/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs +++ b/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs @@ -3,11 +3,14 @@ using AutoMapper; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectMapping; namespace Volo.Abp.AutoMapper { - [DependsOn(typeof(AbpObjectMappingModule))] + [DependsOn( + typeof(AbpObjectMappingModule), + typeof(AbpObjectExtendingModule))] public class AbpAutoMapperModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs index d4ce5cd03a..a5027daec6 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.Abstractions/Volo/Abp/BackgroundJobs/BackgroundJobExecuter.cs @@ -3,7 +3,9 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using System; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; +using Volo.Abp.ExceptionHandling; namespace Volo.Abp.BackgroundJobs { @@ -51,6 +53,10 @@ namespace Volo.Abp.BackgroundJobs { Logger.LogException(ex); + await context.ServiceProvider + .GetRequiredService() + .NotifyAsync(new ExceptionNotificationContext(ex)); + throw new BackgroundJobExecutionException("A background job execution is failed. See inner exception for details.", ex) { JobType = context.JobType.AssemblyQualifiedName, diff --git a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs index 4cb4f04a09..879cf4f8ff 100644 --- a/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs +++ b/framework/src/Volo.Abp.BackgroundJobs.RabbitMQ/Volo/Abp/BackgroundJobs/RabbitMQ/JobQueue.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Options; using Nito.AsyncEx; using RabbitMQ.Client; using RabbitMQ.Client.Events; +using Volo.Abp.ExceptionHandling; using Volo.Abp.RabbitMQ; using Volo.Abp.Threading; @@ -31,6 +32,7 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ protected IRabbitMqSerializer Serializer { get; } protected IBackgroundJobExecuter JobExecuter { get; } protected IServiceScopeFactory ServiceScopeFactory { get; } + protected IExceptionNotifier ExceptionNotifier { get; } protected SemaphoreSlim SyncObj = new SemaphoreSlim(1, 1); protected bool IsDiposed { get; private set; } @@ -41,13 +43,15 @@ namespace Volo.Abp.BackgroundJobs.RabbitMQ IChannelPool channelPool, IRabbitMqSerializer serializer, IBackgroundJobExecuter jobExecuter, - IServiceScopeFactory serviceScopeFactory) + IServiceScopeFactory serviceScopeFactory, + IExceptionNotifier exceptionNotifier) { AbpBackgroundJobOptions = backgroundJobOptions.Value; AbpRabbitMqBackgroundJobOptions = rabbitMqAbpBackgroundJobOptions.Value; Serializer = serializer; JobExecuter = jobExecuter; ServiceScopeFactory = serviceScopeFactory; + ExceptionNotifier = exceptionNotifier; ChannelPool = channelPool; JobConfiguration = AbpBackgroundJobOptions.GetJob(typeof(TArgs)); diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs index 62da163eb9..cb2f6dbfae 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Volo.Abp.ExceptionHandling; using Volo.Abp.Threading; namespace Volo.Abp.BackgroundWorkers @@ -35,18 +36,24 @@ namespace Volo.Abp.BackgroundWorkers private void Timer_Elapsed(object sender, System.EventArgs e) { - try + using (var scope = ServiceScopeFactory.CreateScope()) { - using (var scope = ServiceScopeFactory.CreateScope()) + try { AsyncHelper.RunSync( () => DoWorkAsync(new PeriodicBackgroundWorkerContext(scope.ServiceProvider)) ); } - } - catch (Exception ex) - { - Logger.LogException(ex); + catch (Exception ex) + { + AsyncHelper.RunSync( + () => scope.ServiceProvider + .GetRequiredService() + .NotifyAsync(new ExceptionNotificationContext(ex)) + ); + + Logger.LogException(ex); + } } } diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs index 091bcc917f..c63adaf798 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Volo.Abp.ExceptionHandling; using Volo.Abp.Threading; namespace Volo.Abp.BackgroundWorkers @@ -38,16 +39,21 @@ namespace Volo.Abp.BackgroundWorkers private void Timer_Elapsed(object sender, System.EventArgs e) { - try + using (var scope = ServiceScopeFactory.CreateScope()) { - using (var scope = ServiceScopeFactory.CreateScope()) + try { + DoWork(new PeriodicBackgroundWorkerContext(scope.ServiceProvider)); } - } - catch (Exception ex) - { - Logger.LogException(ex); + catch (Exception ex) + { + scope.ServiceProvider + .GetRequiredService() + .NotifyAsync(new ExceptionNotificationContext(ex)); + + Logger.LogException(ex); + } } } diff --git a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs index c23fb05aa7..267ab259f5 100644 --- a/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs +++ b/framework/src/Volo.Abp.Caching/Volo/Abp/Caching/DistributedCache.cs @@ -2,10 +2,13 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Nito.AsyncEx; +using Volo.Abp.DependencyInjection; +using Volo.Abp.ExceptionHandling; using Volo.Abp.MultiTenancy; using Volo.Abp.Threading; @@ -23,12 +26,14 @@ namespace Volo.Abp.Caching IDistributedCache cache, ICancellationTokenProvider cancellationTokenProvider, IDistributedCacheSerializer serializer, - IDistributedCacheKeyNormalizer keyNormalizer) : base( + IDistributedCacheKeyNormalizer keyNormalizer, + IHybridServiceScopeFactory serviceScopeFactory) : base( distributedCacheOption: distributedCacheOption, cache: cache, cancellationTokenProvider: cancellationTokenProvider, serializer: serializer, - keyNormalizer: keyNormalizer) + keyNormalizer: keyNormalizer, + serviceScopeFactory: serviceScopeFactory) { } @@ -56,6 +61,8 @@ namespace Volo.Abp.Caching protected IDistributedCacheKeyNormalizer KeyNormalizer { get; } + protected IHybridServiceScopeFactory ServiceScopeFactory { get; } + protected SemaphoreSlim SyncSemaphore { get; } protected DistributedCacheEntryOptions DefaultCacheOptions; @@ -67,7 +74,8 @@ namespace Volo.Abp.Caching IDistributedCache cache, ICancellationTokenProvider cancellationTokenProvider, IDistributedCacheSerializer serializer, - IDistributedCacheKeyNormalizer keyNormalizer) + IDistributedCacheKeyNormalizer keyNormalizer, + IHybridServiceScopeFactory serviceScopeFactory) { _distributedCacheOption = distributedCacheOption.Value; Cache = cache; @@ -75,6 +83,7 @@ namespace Volo.Abp.Caching Logger = NullLogger>.Instance; Serializer = serializer; KeyNormalizer = keyNormalizer; + ServiceScopeFactory = serviceScopeFactory; SyncSemaphore = new SemaphoreSlim(1, 1); @@ -139,7 +148,7 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - Logger.LogException(ex, LogLevel.Warning); + AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); return null; } @@ -181,7 +190,7 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - Logger.LogException(ex, LogLevel.Warning); + await HandleExceptionAsync(ex); return null; } @@ -298,7 +307,7 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - Logger.LogException(ex, LogLevel.Warning); + AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); return; } @@ -337,7 +346,7 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - Logger.LogException(ex, LogLevel.Warning); + await HandleExceptionAsync(ex); return; } @@ -364,7 +373,7 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - Logger.LogException(ex, LogLevel.Warning); + AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); return; } @@ -393,7 +402,7 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - Logger.LogException(ex, LogLevel.Warning); + await HandleExceptionAsync(ex); return; } @@ -420,7 +429,8 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - Logger.LogException(ex, LogLevel.Warning); + AsyncHelper.RunSync(() => HandleExceptionAsync(ex)); + return; } throw; @@ -449,12 +459,24 @@ namespace Volo.Abp.Caching { if (hideErrors == true) { - Logger.LogException(ex, LogLevel.Warning); + await HandleExceptionAsync(ex); return; } throw; } } + + protected virtual async Task HandleExceptionAsync(Exception ex) + { + Logger.LogException(ex, LogLevel.Warning); + + using (var scope = ServiceScopeFactory.CreateScope()) + { + await scope.ServiceProvider + .GetRequiredService() + .NotifyAsync(new ExceptionNotificationContext(ex, LogLevel.Warning)); + } + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs index 12fc5af525..accf5eddb9 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/GenerateProxyCommand.cs @@ -57,9 +57,19 @@ namespace Volo.Abp.Cli.Commands var uiFramework = GetUiFramework(commandLineArgs); WebClient client = new WebClient(); - string json = client.DownloadString(apiUrl); - //var sr = File.OpenText("api-definition.json"); - //var json = sr.ReadToEnd(); + string json = ""; + try + { + json = client.DownloadString(apiUrl); + } + catch (Exception ex) + { + throw new CliUsageException( + "Cannot connect to the host {" + apiUrl + "}! Check that the host is up and running." + + Environment.NewLine + Environment.NewLine + + GetUsageInfo() + ); + } Logger.LogInformation("Downloading api definition..."); Logger.LogInformation("Api Url: " + apiUrl); @@ -107,7 +117,7 @@ namespace Volo.Abp.Cli.Commands serviceFileText.AppendLine(""); serviceFileText.AppendLine("@Injectable({providedIn: 'root'})"); serviceFileText.AppendLine("export class [controllerName]Service {"); - serviceFileText.AppendLine(" apiName = '"+ apiName + "';"); + serviceFileText.AppendLine(" apiName = '" + apiName + "';"); serviceFileText.AppendLine(""); serviceFileText.AppendLine(" constructor(private restService: RestService) {}"); serviceFileText.AppendLine(""); @@ -125,7 +135,7 @@ namespace Volo.Abp.Cli.Commands actionName = (char.ToLower(actionName[0]) + actionName.Substring(1)).Replace("Async", "").Replace("Controller", ""); - var returnValueType = (string)action["returnValue"]["type"]; + var returnValueType = (string)action["returnValue"]["type"]; var parameters = action["parameters"]; var parametersText = new StringBuilder(); @@ -140,7 +150,7 @@ namespace Volo.Abp.Cli.Commands var bindingSourceId = (string)parameter["bindingSourceId"]; bindingSourceId = char.ToLower(bindingSourceId[0]) + bindingSourceId.Substring(1); - var name = (string)parameter["name"]; + var name = (string)parameter["name"]; var typeSimple = (string)parameter["typeSimple"]; var typeArray = ((string)parameter["type"]).Split("."); var type = (typeArray[typeArray.Length - 1]).TrimEnd('>'); @@ -201,7 +211,8 @@ namespace Volo.Abp.Cli.Commands { secondTypeList.Add(type); } - else { + else + { firstTypeList.Add(type); } break; @@ -217,7 +228,7 @@ namespace Volo.Abp.Cli.Commands var parameterItemModelPath = $"src/app/{rootPath}/shared/models/{parameterItemModelName}"; if (parameterItem.BindingSourceId == "body" && !File.Exists(parameterItemModelPath)) { - parameterItem.Type = "any"; + parameterItem.Type = "any"; } parametersIndex++; @@ -248,7 +259,7 @@ namespace Volo.Abp.Cli.Commands var firstType = firstTypeArray[firstTypeArray.Length - 1]; var secondTypeArray = returnValueType.Split("<")[1].Split("."); - var secondType = secondTypeArray[secondTypeArray.Length - 1].TrimEnd('>'); + var secondType = secondTypeArray[secondTypeArray.Length - 1].TrimEnd('>'); var secondTypeModelName = secondType.PascalToKebabCase() + ".ts"; var secondTypeModelPath = $"src/app/{rootPath}/shared/models/{secondTypeModelName}"; @@ -405,7 +416,7 @@ namespace Volo.Abp.Cli.Commands } private static string CreateType(JObject data, string returnValueType, string rootPath, List modelIndexList) - { + { var type = data["types"][returnValueType]; if (type == null) @@ -435,9 +446,9 @@ namespace Volo.Abp.Cli.Commands } - var typeModelName = typeName.Replace("<", "").Replace(">", "").Replace("?","").PascalToKebabCase() + ".ts"; + var typeModelName = typeName.Replace("<", "").Replace(">", "").Replace("?", "").PascalToKebabCase() + ".ts"; - var path = $"src/app/{rootPath}/shared/models/{typeModelName}"; + var path = $"src/app/{rootPath}/shared/models/{typeModelName}"; var modelFileText = new StringBuilder(); @@ -474,7 +485,7 @@ namespace Volo.Abp.Cli.Commands if (!string.IsNullOrWhiteSpace(modelIndex)) { modelIndexList.Add(modelIndex); - } + } } } @@ -501,7 +512,7 @@ namespace Volo.Abp.Cli.Commands { var propertyName = (string)property["name"]; propertyName = (char.ToLower(propertyName[0]) + propertyName.Substring(1)); - var typeSimple = (string)property["typeSimple"]; + var typeSimple = (string)property["typeSimple"]; var modelIndex = CreateType(data, (string)property["type"], rootPath, modelIndexList); @@ -536,11 +547,11 @@ namespace Volo.Abp.Cli.Commands ) { var typeSimpleModelName = typeSimple.PascalToKebabCase() + ".ts"; - var modelPath = $"src/app/{rootPath}/shared/models/{typeSimpleModelName}"; + var modelPath = $"src/app/{rootPath}/shared/models/{typeSimpleModelName}"; if (!File.Exists(modelPath)) { typeSimple = "any" + (typeSimple.Contains("[]") ? "[]" : ""); - } + } } if (propertyList.Any(p => p.Key == baseTypeName && p.Value.Any(q => q.Key == propertyName && q.Value == typeSimple))) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs index 3afc32ec4d..8a0c21a254 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs @@ -73,6 +73,12 @@ namespace Volo.Abp.Cli.Commands Logger.LogInformation("UI Framework: " + uiFramework); } + var connectionString = GetConnectionString(commandLineArgs); + if (connectionString != null) + { + Logger.LogInformation("Connection string: " + connectionString); + } + var mobileApp = GetMobilePreference(commandLineArgs); if (mobileApp != MobileApp.None) { @@ -94,7 +100,7 @@ namespace Volo.Abp.Cli.Commands var createSolutionFolder = (commandLineArgs.Options.GetOrNull(Options.CreateSolutionFolder.Short, Options.CreateSolutionFolder.Long) ?? "true").ToLowerInvariant() != "false"; if (!createSolutionFolder) { - Logger.LogInformation("Create Solution Folder: false"); + Logger.LogInformation("Create Solution Folder: no"); } var outputFolder = commandLineArgs.Options.GetOrNull(Options.OutputFolder.Short, Options.OutputFolder.Long); @@ -106,10 +112,7 @@ namespace Volo.Abp.Cli.Commands Path.Combine(outputFolderRoot, SolutionName.Parse(projectName).FullName) : outputFolderRoot; - if (!Directory.Exists(outputFolder)) - { - Directory.CreateDirectory(outputFolder); - } + Volo.Abp.IO.DirectoryHelper.CreateIfNotExists(outputFolder); Logger.LogInformation("Output folder: " + outputFolder); @@ -125,7 +128,8 @@ namespace Volo.Abp.Cli.Commands mobileApp, gitHubLocalRepositoryPath, templateSource, - commandLineArgs.Options + commandLineArgs.Options, + connectionString ) ); @@ -171,6 +175,12 @@ namespace Volo.Abp.Cli.Commands Logger.LogInformation($"'{projectName}' has been successfully created to '{outputFolder}'"); } + private static string GetConnectionString(CommandLineArgs commandLineArgs) + { + var connectionString = commandLineArgs.Options.GetOrNull(Options.ConnectionString.Short, Options.ConnectionString.Long); + return string.IsNullOrWhiteSpace(connectionString) ? null : connectionString; + } + public string GetUsageInfo() { var sb = new StringBuilder(); @@ -208,6 +218,7 @@ namespace Volo.Abp.Cli.Commands sb.AppendLine(" abp new Acme.BookStore -t module --no-ui"); sb.AppendLine(" abp new Acme.BookStore -ts \"D:\\localTemplate\\abp\""); sb.AppendLine(" abp new Acme.BookStore --local-framework-ref --abp-path \"D:\\github\\abp\""); + sb.AppendLine(" abp new Acme.BookStore --connection-string \"Server=myServerName\\myInstanceName;Database=myDatabase;User Id=myUsername;Password=myPassword\""); sb.AppendLine(""); sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI"); @@ -312,6 +323,12 @@ namespace Volo.Abp.Cli.Commands public const string Long = "template-source"; } + public static class ConnectionString + { + public const string Short = "cs"; + public const string Long = "connection-string"; + } + public static class CreateSolutionFolder { public const string Short = "csf"; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs index 1ffdefbe61..0aa2587e20 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/AbpIoSourceCodeStore.cs @@ -2,11 +2,16 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net.Http; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Volo.Abp.Cli.Http; +using Volo.Abp.Cli.ProjectBuilding.Templates.App; +using Volo.Abp.Cli.ProjectBuilding.Templates.MvcModule; using Volo.Abp.DependencyInjection; using Volo.Abp.Http; using Volo.Abp.IO; @@ -48,16 +53,33 @@ namespace Volo.Abp.Cli.ProjectBuilding string templateSource = null) { + DirectoryHelper.CreateIfNotExists(CliPaths.TemplateCache); + var latestVersion = await GetLatestSourceCodeVersionAsync(name, type); if (version == null) { + if (latestVersion == null) + { + Logger.LogWarning("The remote service is currently unavailable, please specify the version."); + Logger.LogWarning(string.Empty); + Logger.LogWarning("Find the following template in your cache directory: "); + Logger.LogWarning("\t Template Name\tVersion"); + + var templateList = GetLocalTemplates(); + foreach (var cacheFile in templateList) + { + Logger.LogWarning($"\t {cacheFile.TemplateName}\t\t{cacheFile.Version}"); + } + + Logger.LogWarning(string.Empty); + throw new CliUsageException("Use command: abp new Acme.BookStore -v version"); + } + version = latestVersion; } var nugetVersion = (await GetTemplateNugetVersionAsync(name, type, version)) ?? version; - - DirectoryHelper.CreateIfNotExists(CliPaths.TemplateCache); - + if (!string.IsNullOrWhiteSpace(templateSource) && !IsNetworkSource(templateSource)) { Logger.LogInformation("Using local " + type + ": " + name + ", version: " + version); @@ -122,7 +144,7 @@ namespace Volo.Abp.Cli.ProjectBuilding catch (Exception ex) { Console.WriteLine("Error occured while getting the latest version from {0} : {1}", url, ex.Message); - throw; + return null; } } @@ -195,11 +217,30 @@ namespace Volo.Abp.Cli.ProjectBuilding } } - private static bool IsNetworkSource(string source) + private bool IsNetworkSource(string source) { return source.ToLower().StartsWith("http"); } + private List<(string TemplateName, string Version)> GetLocalTemplates() + { + var templateList = new List<(string TemplateName, string Version)>(); + + var stringBuilder = new StringBuilder(); + foreach (var cacheFile in Directory.GetFiles(CliPaths.TemplateCache)) + { + stringBuilder.AppendLine(cacheFile); + } + + var matches = Regex.Matches(stringBuilder.ToString(),$"({AppTemplate.TemplateName}|{AppProTemplate.TemplateName}|{ModuleTemplate.TemplateName}|{ModuleProTemplate.TemplateName})-(.+).zip"); + foreach (Match match in matches) + { + templateList.Add((match.Groups[1].Value, match.Groups[2].Value)); + } + + return templateList; + } + public class SourceCodeDownloadInputDto { public string Name { get; set; } diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Analyticses/CliAnalyticsCollect.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Analyticses/CliAnalyticsCollect.cs index fdfbb8b5d3..621631af2a 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Analyticses/CliAnalyticsCollect.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Analyticses/CliAnalyticsCollect.cs @@ -1,4 +1,5 @@ -using System.Net.Http; +using System; +using System.Net.Http; using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -32,27 +33,36 @@ namespace Volo.Abp.Cli.ProjectBuilding.Analyticses public async Task CollectAsync(CliAnalyticsCollectInputDto input) { var postData = _jsonSerializer.Serialize(input); - using (var client = new CliHttpClient()) + var url = $"{CliUrls.WwwAbpIo}api/clianalytics/collect"; + + try { - var responseMessage = await client.PostAsync( - $"{CliUrls.WwwAbpIo}api/clianalytics/collect", - new StringContent(postData, Encoding.UTF8, MimeTypes.Application.Json), - _cancellationTokenProvider.Token - ); - - if (!responseMessage.IsSuccessStatusCode) + using (var client = new CliHttpClient()) { - var exceptionMessage = "Remote server returns '" + (int)responseMessage.StatusCode + "-" + responseMessage.ReasonPhrase + "'. "; - var remoteServiceErrorMessage = await _remoteServiceExceptionHandler.GetAbpRemoteServiceErrorAsync(responseMessage); + var responseMessage = await client.PostAsync( + url, + new StringContent(postData, Encoding.UTF8, MimeTypes.Application.Json), + _cancellationTokenProvider.Token + ); - if (remoteServiceErrorMessage != null) + if (!responseMessage.IsSuccessStatusCode) { - exceptionMessage += remoteServiceErrorMessage; - } + var exceptionMessage = "Remote server returns '" + (int)responseMessage.StatusCode + "-" + responseMessage.ReasonPhrase + "'. "; + var remoteServiceErrorMessage = await _remoteServiceExceptionHandler.GetAbpRemoteServiceErrorAsync(responseMessage); + + if (remoteServiceErrorMessage != null) + { + exceptionMessage += remoteServiceErrorMessage; + } - _logger.LogInformation(exceptionMessage); + _logger.LogInformation(exceptionMessage); + } } } + catch (Exception ex) + { + // ignored + } } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs new file mode 100644 index 0000000000..b1e118ee8b --- /dev/null +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Volo.Abp.Cli.ProjectBuilding.Files; +using Volo.Abp.Text; + +namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps +{ + public class ConnectionStringChangeStep : ProjectBuildPipelineStep + { + private const string DefaultConnectionStringKey = "Default"; + + public override void Execute(ProjectBuildContext context) + { + var appSettingsJsonFiles = context.Files.Where(f => + f.Name.EndsWith("appsettings.json", StringComparison.OrdinalIgnoreCase)) + .ToArray(); + + if (!appSettingsJsonFiles.Any()) + { + return; + } + + var newConnectionString = $"\"{DefaultConnectionStringKey}\": \"{context.BuildArgs.ConnectionString}\""; + + foreach (var appSettingsJson in appSettingsJsonFiles) + { + try + { + var appSettingJsonContentWithoutBom = StringHelper.ConvertFromBytesWithoutBom(appSettingsJson.Bytes); + var jsonObject = JObject.Parse(appSettingJsonContentWithoutBom); + + var connectionStringContainer = (JContainer)jsonObject?["ConnectionStrings"]; + if (connectionStringContainer == null) + { + continue; + } + + if (!connectionStringContainer.Any()) + { + continue; + } + + var connectionStrings = connectionStringContainer.ToList(); + + foreach (var connectionString in connectionStrings) + { + var property = ((JProperty)connectionString); + var connectionStringName = property.Name; + + if (connectionStringName == DefaultConnectionStringKey) + { + var defaultConnectionString = property.ToString(); + if (defaultConnectionString == null) + { + continue; + } + + appSettingsJson.ReplaceText(defaultConnectionString, newConnectionString); + break; + } + } + } + catch (Exception ex) + { + Console.WriteLine("Cannot change the connection string in " + appSettingsJson.Name + ". Error: " + ex.Message); + } + } + } + } +} diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs index a68de8ef62..607ec4f1c7 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs @@ -28,6 +28,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building pipeline.Steps.Add(new RemoveRootFolderStep()); } + if (context.BuildArgs.ConnectionString != null) + { + pipeline.Steps.Add(new ConnectionStringChangeStep()); + } + pipeline.Steps.Add(new CreateProjectResultZipStep()); return pipeline; diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs index ea207bc426..70cb98dff0 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs @@ -27,6 +27,9 @@ namespace Volo.Abp.Cli.ProjectBuilding [CanBeNull] public string TemplateSource { get; set; } + [CanBeNull] + public string ConnectionString { get; set; } + [NotNull] public Dictionary ExtraProperties { get; set; } @@ -39,7 +42,8 @@ namespace Volo.Abp.Cli.ProjectBuilding MobileApp? mobileApp = null, [CanBeNull] string abpGitHubLocalRepositoryPath = null, [CanBeNull] string templateSource = null, - Dictionary extraProperties = null) + Dictionary extraProperties = null, + [CanBeNull] string connectionString = null) { SolutionName = Check.NotNull(solutionName, nameof(solutionName)); TemplateName = templateName; @@ -50,6 +54,7 @@ namespace Volo.Abp.Cli.ProjectBuilding AbpGitHubLocalRepositoryPath = abpGitHubLocalRepositoryPath; TemplateSource = templateSource; ExtraProperties = extraProperties ?? new Dictionary(); + ConnectionString = connectionString; } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Check.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Check.cs index f942f44b0f..7001925745 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Check.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Check.cs @@ -121,6 +121,21 @@ namespace Volo.Abp return value; } + [ContractAnnotation("type:null => halt")] + public static Type AssignableTo( + Type type, + [InvokerParameterName] [NotNull] string parameterName) + { + NotNull(type, parameterName); + + if (!type.IsAssignableTo()) + { + throw new ArgumentException($"{parameterName} (type of {type.AssemblyQualifiedName}) should be assignable to the {typeof(TBaseType).GetFullNameWithAssemblyName()}!"); + } + + return type; + } + public static string Length( [CanBeNull] string value, [InvokerParameterName] [NotNull] string parameterName, diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs index cb0d727795..cf4befae5b 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/DependencyInjection/ExposedServiceExplorer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; namespace Volo.Abp.DependencyInjection { @@ -17,10 +16,11 @@ namespace Volo.Abp.DependencyInjection public static List GetExposedServices(Type type) { return type - .GetCustomAttributes() + .GetCustomAttributes(true) .OfType() .DefaultIfEmpty(DefaultExposeServicesAttribute) .SelectMany(p => p.GetExposedServiceTypes(type)) + .Distinct() .ToList(); } } diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotificationContext.cs b/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotificationContext.cs new file mode 100644 index 0000000000..6f7fbda0af --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotificationContext.cs @@ -0,0 +1,32 @@ +using System; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; + +namespace Volo.Abp.ExceptionHandling +{ + public class ExceptionNotificationContext + { + /// + /// The exception object. + /// + [NotNull] + public Exception Exception { get; } + + public LogLevel LogLevel { get; } + + /// + /// True, if it is handled. + /// + public bool Handled { get; } + + public ExceptionNotificationContext( + [NotNull] Exception exception, + LogLevel? logLevel = null, + bool handled = true) + { + Exception = Check.NotNull(exception, nameof(exception)); + LogLevel = logLevel ?? exception.GetLogLevel(); + Handled = handled; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotifier.cs b/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotifier.cs new file mode 100644 index 0000000000..b9076c471b --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotifier.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.ExceptionHandling +{ + public class ExceptionNotifier : IExceptionNotifier, ITransientDependency + { + public ILogger Logger { get; set; } + + protected IEnumerable ExceptionSubscribers { get; } + + public ExceptionNotifier(IEnumerable exceptionSubscribers) + { + ExceptionSubscribers = exceptionSubscribers; + Logger = NullLogger.Instance; + } + + public virtual async Task NotifyAsync([NotNull] ExceptionNotificationContext context) + { + Check.NotNull(context, nameof(context)); + + foreach (var exceptionSubscriber in ExceptionSubscribers) + { + try + { + await exceptionSubscriber.HandleAsync(context); + } + catch (Exception e) + { + Logger.LogWarning($"Exception subscriber of type {exceptionSubscriber.GetType().AssemblyQualifiedName} has thrown an exception!"); + Logger.LogException(e, LogLevel.Warning); + } + } + } + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotifierExtensions.cs b/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotifierExtensions.cs new file mode 100644 index 0000000000..493c0cf1f3 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionNotifierExtensions.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.Logging; + +namespace Volo.Abp.ExceptionHandling +{ + public static class ExceptionNotifierExtensions + { + public static Task NotifyAsync( + [NotNull] this IExceptionNotifier exceptionNotifier, + [NotNull] Exception exception, + LogLevel? logLevel = null, + bool handled = true) + { + Check.NotNull(exceptionNotifier, nameof(exceptionNotifier)); + + return exceptionNotifier.NotifyAsync( + new ExceptionNotificationContext( + exception, + logLevel, + handled + ) + ); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionSubscriber.cs b/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionSubscriber.cs new file mode 100644 index 0000000000..5f2971c8c4 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/ExceptionSubscriber.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.ExceptionHandling +{ + [ExposeServices(typeof(IExceptionSubscriber))] + public abstract class ExceptionSubscriber : IExceptionSubscriber, ITransientDependency + { + public abstract Task HandleAsync(ExceptionNotificationContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/IExceptionNotifier.cs b/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/IExceptionNotifier.cs new file mode 100644 index 0000000000..5646c28e5e --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/IExceptionNotifier.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.ExceptionHandling +{ + public interface IExceptionNotifier + { + Task NotifyAsync([NotNull] ExceptionNotificationContext context); + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/IExceptionSubscriber.cs b/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/IExceptionSubscriber.cs new file mode 100644 index 0000000000..dc7f336970 --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/ExceptionHandling/IExceptionSubscriber.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace Volo.Abp.ExceptionHandling +{ + public interface IExceptionSubscriber + { + Task HandleAsync([NotNull] ExceptionNotificationContext context); + } +} diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs index 21ca7c2086..a945784352 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.IO; using System.Text; using System.Threading.Tasks; using JetBrains.Annotations; +using Volo.Abp.Text; namespace Volo.Abp.IO { @@ -73,6 +75,63 @@ namespace Volo.Abp.IO } } - //TODO: ReadAllLinesAsync + /// + /// Opens a text file, reads all lines of the file, and then closes the file. + /// + /// The file to open for reading. + /// Encoding of the file. Default is UTF8 + /// Specifies how the operating system should open a file. Default is Open + /// Defines constants for read, write, or read/write access to a file. Default is Read + /// Contains constants for controlling the kind of access other FileStream objects can have to the same file. Default is Read + /// Length of StreamReader buffer. Default is 4096. + /// Indicates FileStream options. Default is Asynchronous (The file is to be used for asynchronous reading.) and SequentialScan (The file is to be accessed sequentially from beginning to end.) + /// A string containing all lines of the file. + public static async Task ReadAllLinesAsync(string path, + Encoding encoding = null, + FileMode fileMode = FileMode.Open, + FileAccess fileAccess = FileAccess.Read, + FileShare fileShare = FileShare.Read, + int bufferSize = 4096, + FileOptions fileOptions = FileOptions.Asynchronous | FileOptions.SequentialScan) + { + if (encoding == null) + { + encoding = Encoding.UTF8; + } + + var lines = new List(); + + using (var stream = new FileStream( + path, + fileMode, + fileAccess, + fileShare, + bufferSize, + fileOptions)) + { + using (var reader = new StreamReader(stream, encoding)) + { + string line; + while ((line = await reader.ReadLineAsync()) != null) + { + lines.Add(line); + } + } + } + + return lines.ToArray(); + } + + /// + /// Opens a text file, reads content without BOM + /// + /// The file to open for reading. + /// A string containing all lines of the file. + public static async Task ReadFileWithoutBomAsync(string path) + { + var content = await ReadAllBytesAsync(path); + + return StringHelper.ConvertFromBytesWithoutBom(content); + } } } \ No newline at end of file diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Text/StringHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Text/StringHelper.cs new file mode 100644 index 0000000000..c6be5db02b --- /dev/null +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Text/StringHelper.cs @@ -0,0 +1,37 @@ +using System.Text; + +namespace Volo.Abp.Text +{ + public class StringHelper + { + /// + /// Converts a byte[] to string without BOM (byte order mark). + /// + /// The byte[] to be converted to string + /// The encoding to get string. Default is UTF8 + /// + public static string ConvertFromBytesWithoutBom(byte[] bytes, Encoding encoding = null) + { + if (bytes == null) + { + return null; + } + + if (encoding == null) + { + encoding = Encoding.UTF8; + } + + var hasBom = bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF; + + if (hasBom) + { + return encoding.GetString(bytes, 3, bytes.Length - 3); + } + else + { + return encoding.GetString(bytes); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj b/framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj index 7a53782ae1..cde2718a56 100644 --- a/framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj +++ b/framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj @@ -16,6 +16,7 @@ + diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs index 2414803970..8c9643012e 100644 --- a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs +++ b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs @@ -2,13 +2,15 @@ using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; using Volo.Abp.Uow; namespace Volo.Abp.Data { [DependsOn( + typeof(AbpObjectExtendingModule), typeof(AbpUnitOfWorkModule) - )] + )] public class AbpDataModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs index c5dd43de14..8a8e99d869 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs @@ -5,6 +5,7 @@ namespace Volo.Abp.Application.Dtos { /// /// This class can be inherited by DTO classes to implement interface. + /// It has the and objects as a DTOs represent the related user. /// /// Type of the User DTO [Serializable] @@ -19,6 +20,7 @@ namespace Volo.Abp.Application.Dtos /// /// This class can be inherited by DTO classes to implement interface. + /// It has the and objects as a DTOs represent the related user. /// /// Type of primary key /// Type of the User DTO diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs index 51e3c9018a..5d6260ebcd 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs @@ -5,6 +5,7 @@ namespace Volo.Abp.Application.Dtos { /// /// This class can be inherited by DTO classes to implement interface. + /// It also has the object as a DTO represents the user. /// /// Type of the User DTO [Serializable] @@ -14,7 +15,8 @@ namespace Volo.Abp.Application.Dtos } /// - /// This class can be inherited by DTO classes to implement interface. + /// This class can be inherited by DTO classes to implement interface. + /// It also has the object as a DTO represents the user. /// /// Type of primary key /// Type of the User DTO diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs new file mode 100644 index 0000000000..6be7d38070 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs @@ -0,0 +1,35 @@ +using System; +using Volo.Abp.Auditing; +using Volo.Abp.Data; + +namespace Volo.Abp.Application.Dtos +{ + /// + /// This class can be inherited by DTO classes to implement interface. + /// It also implements the interface. + /// + /// Type of primary key + [Serializable] + public abstract class ExtensibleAuditedEntityDto : ExtensibleCreationAuditedEntityDto, IAuditedObject + { + /// + public DateTime? LastModificationTime { get; set; } + + /// + public Guid? LastModifierId { get; set; } + } + + /// + /// This class can be inherited by DTO classes to implement interface. + /// It also implements the interface. + /// + [Serializable] + public abstract class ExtensibleAuditedEntityDto : ExtensibleCreationAuditedEntityDto, IAuditedObject + { + /// + public DateTime? LastModificationTime { get; set; } + + /// + public Guid? LastModifierId { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs new file mode 100644 index 0000000000..94086c119b --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs @@ -0,0 +1,40 @@ +using System; +using Volo.Abp.Auditing; +using Volo.Abp.Data; + +namespace Volo.Abp.Application.Dtos +{ + /// + /// This class can be inherited by DTO classes to implement interface. + /// It has the and objects as a DTOs represent the related user. + /// It also implements the interface. + /// + /// Type of primary key + /// Type of the User DTO + [Serializable] + public abstract class ExtensibleAuditedEntityWithUserDto : ExtensibleAuditedEntityDto, IAuditedObject + { + /// + public TUserDto Creator { get; set; } + + /// + public TUserDto LastModifier { get; set; } + } + + /// + /// This class can be inherited by DTO classes to implement interface. + /// It has the and objects as a DTOs represent the related user. + /// It also implements the interface. + /// + /// Type of the User DTO + [Serializable] + public abstract class ExtensibleAuditedEntityWithUserDto : ExtensibleAuditedEntityDto, + IAuditedObject + { + /// + public TUserDto Creator { get; set; } + + /// + public TUserDto LastModifier { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs new file mode 100644 index 0000000000..1de40ff4d5 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs @@ -0,0 +1,35 @@ +using System; +using Volo.Abp.Auditing; +using Volo.Abp.Data; + +namespace Volo.Abp.Application.Dtos +{ + /// + /// This class can be inherited by DTO classes to implement interface. + /// It also implements the interface. + /// + /// Type of primary key + [Serializable] + public abstract class ExtensibleCreationAuditedEntityDto : ExtensibleEntityDto, ICreationAuditedObject + { + /// + public DateTime CreationTime { get; set; } + + /// + public Guid? CreatorId { get; set; } + } + + /// + /// This class can be inherited by DTO classes to implement interface. + /// It also implements the interface. + /// + [Serializable] + public abstract class ExtensibleCreationAuditedEntityDto : ExtensibleEntityDto, ICreationAuditedObject + { + /// + public DateTime CreationTime { get; set; } + + /// + public Guid? CreatorId { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs new file mode 100644 index 0000000000..d5352d5b03 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs @@ -0,0 +1,32 @@ +using System; +using Volo.Abp.Auditing; +using Volo.Abp.Data; + +namespace Volo.Abp.Application.Dtos +{ + /// + /// This class can be inherited by DTO classes to implement interface. + /// It has the object as a DTO represents the user. + /// It also implements the interface. + /// + /// Type of primary key + /// Type of the User DTO + [Serializable] + public abstract class ExtensibleCreationAuditedEntityWithUserDto : ExtensibleCreationAuditedEntityDto, ICreationAuditedObject + { + public TUserDto Creator { get; set; } + } + + /// + /// This class can be inherited by DTO classes to implement interface. + /// It has the object as a DTO represents the user. + /// It also implements the interface. + /// + /// Type of the User DTO + [Serializable] + public abstract class ExtensibleCreationAuditedEntityWithUserDto : ExtensibleCreationAuditedEntityDto, + ICreationAuditedObject + { + public TUserDto Creator { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs new file mode 100644 index 0000000000..63d07bcdcf --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs @@ -0,0 +1,28 @@ +using System; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.Application.Dtos +{ + [Serializable] + public abstract class ExtensibleEntityDto : ExtensibleObject, IEntityDto + { + /// + /// Id of the entity. + /// + public TKey Id { get; set; } + + public override string ToString() + { + return $"[DTO: {GetType().Name}] Id = {Id}"; + } + } + + [Serializable] + public abstract class ExtensibleEntityDto : ExtensibleObject, IEntityDto + { + public override string ToString() + { + return $"[DTO: {GetType().Name}]"; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityDto.cs new file mode 100644 index 0000000000..d72bf8135d --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityDto.cs @@ -0,0 +1,41 @@ +using System; +using Volo.Abp.Auditing; +using Volo.Abp.Data; + +namespace Volo.Abp.Application.Dtos +{ + /// + /// This class can be inherited by DTO classes to implement interface. + /// It also implements the interface. + /// + /// Type of primary key + [Serializable] + public abstract class ExtensibleFullAuditedEntityDto : ExtensibleAuditedEntityDto, IFullAuditedObject + { + /// + public bool IsDeleted { get; set; } + + /// + public Guid? DeleterId { get; set; } + + /// + public DateTime? DeletionTime { get; set; } + } + + /// + /// This class can be inherited by DTO classes to implement interface. + /// It also implements the interface. + /// + [Serializable] + public abstract class ExtensibleFullAuditedEntityDto : ExtensibleAuditedEntityDto, IFullAuditedObject + { + /// + public bool IsDeleted { get; set; } + + /// + public Guid? DeleterId { get; set; } + + /// + public DateTime? DeletionTime { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityWithUserDto.cs new file mode 100644 index 0000000000..b5e68e7cb7 --- /dev/null +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityWithUserDto.cs @@ -0,0 +1,46 @@ +using System; +using Volo.Abp.Auditing; +using Volo.Abp.Data; + +namespace Volo.Abp.Application.Dtos +{ + /// + /// This class can be inherited by DTO classes to implement interface. + /// It has the , and objects as a DTOs represent the related user. + /// It also implements the interface. + /// + /// Type of primary key + /// Type of the User + [Serializable] + public abstract class ExtensibleFullAuditedEntityWithUserDto : ExtensibleFullAuditedEntityDto, IFullAuditedObject + { + /// + public TUserDto Creator { get; set; } + + /// + public TUserDto LastModifier { get; set; } + + /// + public TUserDto Deleter { get; set; } + } + + /// + /// This class can be inherited by DTO classes to implement interface. + /// It has the , and objects as a DTOs represent the related user. + /// It also implements the interface. + /// + /// Type of the User + [Serializable] + public abstract class ExtensibleFullAuditedEntityWithUserDto : ExtensibleFullAuditedEntityDto, + IFullAuditedObject + { + /// + public TUserDto Creator { get; set; } + + /// + public TUserDto LastModifier { get; set; } + + /// + public TUserDto Deleter { get; set; } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/FullAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/FullAuditedEntityWithUserDto.cs index 91e31250fb..30b1f0b2ba 100644 --- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/FullAuditedEntityWithUserDto.cs +++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/FullAuditedEntityWithUserDto.cs @@ -4,7 +4,8 @@ using Volo.Abp.Auditing; namespace Volo.Abp.Application.Dtos { /// - /// This class can be inherited by DTO classes to implement interface. + /// This class can be inherited by DTO classes to implement interface. + /// It has the , and objects as a DTOs represent the related user. /// /// Type of the User [Serializable] @@ -21,7 +22,8 @@ namespace Volo.Abp.Application.Dtos } /// - /// This class can be inherited by DTO classes to implement interface. + /// This class can be inherited by DTO classes to implement interface. + /// It has the , and objects as a DTOs represent the related user. /// /// Type of primary key /// Type of the User diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj index ebad209a50..d565f6c114 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj @@ -19,7 +19,7 @@ - + diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 6cfbe3d01c..3ba7092395 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -22,6 +22,7 @@ using Volo.Abp.EntityFrameworkCore.Modeling; using Volo.Abp.EntityFrameworkCore.ValueConverters; using Volo.Abp.Guids; using Volo.Abp.MultiTenancy; +using Volo.Abp.ObjectExtending; using Volo.Abp.Reflection; using Volo.Abp.Timing; using Volo.Abp.Uow; @@ -155,6 +156,58 @@ namespace Volo.Abp.EntityFrameworkCore ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges; ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; + + ChangeTracker.Tracked += ChangeTracker_Tracked; + } + + protected virtual void ChangeTracker_Tracked(object sender, EntityTrackedEventArgs e) + { + FillExtraPropertiesForTrackedEntities(e); + } + + protected virtual void FillExtraPropertiesForTrackedEntities(EntityTrackedEventArgs e) + { + var entityType = e.Entry.Metadata.ClrType; + if (entityType == null) + { + return; + } + + if (!(e.Entry.Entity is IHasExtraProperties entity)) + { + return; + } + + if (!e.FromQuery) + { + return; + } + + var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType); + if (objectExtension == null) + { + return; + } + + foreach (var property in objectExtension.GetProperties()) + { + if (!property.IsMappedToFieldForEfCore()) + { + continue; + } + /* Checking "currentValue != null" has a good advantage: + * Assume that you we already using a named extra property, + * then decided to create a field (entity extension) for it. + * In this way, it prevents to delete old value in the JSON and + * updates the field on the next save! + */ + + var currentValue = e.Entry.CurrentValues[property.Name]; + if (currentValue != null) + { + entity.SetProperty(property.Name, currentValue); + } + } } protected virtual EntityChangeReport ApplyAbpConcepts() @@ -184,9 +237,46 @@ namespace Volo.Abp.EntityFrameworkCore break; } + HandleExtraPropertiesOnSave(entry); + AddDomainEvents(changeReport, entry.Entity); } + protected virtual void HandleExtraPropertiesOnSave(EntityEntry entry) + { + if (entry.State.IsIn(EntityState.Deleted, EntityState.Unchanged)) + { + return; + } + + var entityType = entry.Metadata.ClrType; + if (entityType == null) + { + return; + } + + if (!(entry.Entity is IHasExtraProperties entity)) + { + return; + } + + var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType); + if (objectExtension == null) + { + return; + } + + foreach (var property in objectExtension.GetProperties()) + { + if (!entity.HasProperty(property.Name)) + { + continue; + } + + entry.Property(property.Name).CurrentValue = entity.GetProperty(property.Name); + } + } + protected virtual void ApplyAbpConceptsForAddedEntity(EntityEntry entry, EntityChangeReport changeReport) { CheckAndSetId(entry); diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs index 6db09d4d17..0a84b6871d 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs @@ -8,6 +8,7 @@ using Volo.Abp.Domain.Entities; using Volo.Abp.EntityFrameworkCore.ValueComparers; using Volo.Abp.EntityFrameworkCore.ValueConverters; using Volo.Abp.MultiTenancy; +using Volo.Abp.ObjectExtending; namespace Volo.Abp.EntityFrameworkCore.Modeling { @@ -17,6 +18,7 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling { b.TryConfigureConcurrencyStamp(); b.TryConfigureExtraProperties(); + b.TryConfigureObjectExtensions(); b.TryConfigureMayHaveCreator(); b.TryConfigureMustHaveCreator(); b.TryConfigureSoftDelete(); @@ -25,7 +27,7 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling b.TryConfigureCreationTime(); b.TryConfigureLastModificationTime(); b.TryConfigureModificationAudited(); - b.TryConfigureMultiTenant(); + b.TryConfigureMultiTenant(); } public static void ConfigureConcurrencyStamp(this EntityTypeBuilder b) @@ -53,13 +55,30 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling public static void TryConfigureExtraProperties(this EntityTypeBuilder b) { - //TODO: Max length? + if (!b.Metadata.ClrType.IsAssignableTo()) + { + return; + } + + b.Property>(nameof(IHasExtraProperties.ExtraProperties)) + .HasColumnName(nameof(IHasExtraProperties.ExtraProperties)) + .HasConversion(new ExtraPropertiesValueConverter(b.Metadata.ClrType)) + .Metadata.SetValueComparer(new AbpDictionaryValueComparer()); + + b.TryConfigureObjectExtensions(); + } + + public static void ConfigureObjectExtensions(this EntityTypeBuilder b) + where T : class, IHasExtraProperties + { + b.As().TryConfigureObjectExtensions(); + } + + public static void TryConfigureObjectExtensions(this EntityTypeBuilder b) + { if (b.Metadata.ClrType.IsAssignableTo()) { - b.Property>(nameof(IHasExtraProperties.ExtraProperties)) - .HasColumnName(nameof(IHasExtraProperties.ExtraProperties)) - .HasConversion(new AbpJsonValueConverter>()) - .Metadata.SetValueComparer(new AbpDictionaryValueComparer()); + ObjectExtensionManager.Instance.ConfigureEfCoreEntity(b); } } @@ -276,14 +295,13 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling } public static void ConfigureFullAuditedAggregateRoot(this EntityTypeBuilder b) - where T : class + where T : class { b.As().TryConfigureFullAudited(); b.As().TryConfigureExtraProperties(); b.As().TryConfigureConcurrencyStamp(); } - //TODO: Add other interfaces (IAuditedObject...) } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs index e77b2842a8..6ecf19fa98 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/AbpJsonValueConverter.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Newtonsoft.Json; +using Volo.Abp.Data; namespace Volo.Abp.EntityFrameworkCore.ValueConverters { @@ -7,9 +8,20 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters { public AbpJsonValueConverter() : base( - d => JsonConvert.SerializeObject(d, Formatting.None), - s => JsonConvert.DeserializeObject(s)) + d => SerializeObject(d), + s => DeserializeObject(s)) { + + } + + private static string SerializeObject(TPropertyType d) + { + return JsonConvert.SerializeObject(d, Formatting.None); + } + + private static TPropertyType DeserializeObject(string s) + { + return JsonConvert.DeserializeObject(s); } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs new file mode 100644 index 0000000000..ddcdb942ba --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Newtonsoft.Json; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.EntityFrameworkCore.ValueConverters +{ + public class ExtraPropertiesValueConverter : ValueConverter, string> + { + public ExtraPropertiesValueConverter(Type entityType) + : base( + d => SerializeObject(d, entityType), + s => DeserializeObject(s)) + { + + } + + private static string SerializeObject(Dictionary extraProperties, Type entityType) + { + var copyDictionary = new Dictionary(extraProperties); + + if (entityType != null) + { + var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType); + if (objectExtension != null) + { + foreach (var property in objectExtension.GetProperties()) + { + if (property.IsMappedToFieldForEfCore()) + { + copyDictionary.Remove(property.Name); + } + } + } + } + + return JsonConvert.SerializeObject(copyDictionary, Formatting.None); + } + + private static Dictionary DeserializeObject(string extraPropertiesAsJson) + { + return JsonConvert.DeserializeObject>(extraPropertiesAsJson); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs new file mode 100644 index 0000000000..ef3d87119a --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs @@ -0,0 +1,41 @@ +using System; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Volo.Abp.ObjectExtending +{ + public static class EfCoreObjectExtensionInfoExtensions + { + public static ObjectExtensionInfo MapEfCoreProperty( + [NotNull] this ObjectExtensionInfo objectExtensionInfo, + [NotNull] string propertyName, + [CanBeNull] Action propertyBuildAction) + { + return objectExtensionInfo.MapEfCoreProperty( + typeof(TProperty), + propertyName, + propertyBuildAction + ); + } + + public static ObjectExtensionInfo MapEfCoreProperty( + [NotNull] this ObjectExtensionInfo objectExtensionInfo, + [NotNull] Type propertyType, + [NotNull] string propertyName, + [CanBeNull] Action propertyBuildAction) + { + Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo)); + + return objectExtensionInfo.AddOrUpdateProperty( + propertyType, + propertyName, + options => + { + options.MapEfCore( + propertyBuildAction + ); + } + ); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs new file mode 100644 index 0000000000..52dca0c105 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs @@ -0,0 +1,80 @@ +using System; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Volo.Abp.Data; +using Volo.Abp.Domain.Entities; + +namespace Volo.Abp.ObjectExtending +{ + public static class EfCoreObjectExtensionManagerExtensions + { + public static ObjectExtensionManager MapEfCoreProperty( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] string propertyName, + [CanBeNull] Action propertyBuildAction = null) + where TEntity : IHasExtraProperties, IEntity + { + return objectExtensionManager.MapEfCoreProperty( + typeof(TEntity), + typeof(TProperty), + propertyName, + propertyBuildAction + ); + } + + public static ObjectExtensionManager MapEfCoreProperty( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] Type entityType, + [NotNull] Type propertyType, + [NotNull] string propertyName, + [CanBeNull] Action propertyBuildAction = null) + { + Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); + + return objectExtensionManager.AddOrUpdateProperty( + entityType, + propertyType, + propertyName, + options => + { + options.MapEfCore( + propertyBuildAction + ); + } + ); + } + + public static void ConfigureEfCoreEntity( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] EntityTypeBuilder typeBuilder) + { + Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); + Check.NotNull(typeBuilder, nameof(typeBuilder)); + + var objectExtension = objectExtensionManager.GetOrNull(typeBuilder.Metadata.ClrType); + if (objectExtension == null) + { + return; + } + + foreach (var property in objectExtension.GetProperties()) + { + var efCoreMapping = property.GetEfCoreMappingOrNull(); + if (efCoreMapping == null) + { + continue; + } + + /* Prevent multiple calls to the entityTypeBuilder.Property(...) method */ + if (typeBuilder.Metadata.FindProperty(property.Name) != null) + { + continue; + } + + var propertyBuilder = typeBuilder.Property(property.Type, property.Name); + + efCoreMapping.PropertyBuildAction?.Invoke(propertyBuilder); + } + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs new file mode 100644 index 0000000000..fda5d88ceb --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Volo.Abp.ObjectExtending +{ + public static class EfCoreObjectExtensionPropertyInfoExtensions + { + public const string EfCorePropertyConfigurationName = "EfCoreMapping"; + + [NotNull] + public static ObjectExtensionPropertyInfo MapEfCore( + [NotNull] this ObjectExtensionPropertyInfo propertyExtension, + [CanBeNull] Action propertyBuildAction = null) + { + Check.NotNull(propertyExtension, nameof(propertyExtension)); + + propertyExtension.Configuration[EfCorePropertyConfigurationName] = + new ObjectExtensionPropertyInfoEfCoreMappingOptions( + propertyExtension, + propertyBuildAction + ); + + return propertyExtension; + } + + [CanBeNull] + public static ObjectExtensionPropertyInfoEfCoreMappingOptions GetEfCoreMappingOrNull( + [NotNull] this ObjectExtensionPropertyInfo propertyExtension) + { + Check.NotNull(propertyExtension, nameof(propertyExtension)); + + return propertyExtension + .Configuration + .GetOrDefault(EfCorePropertyConfigurationName) + as ObjectExtensionPropertyInfoEfCoreMappingOptions; + } + + public static bool IsMappedToFieldForEfCore( + [NotNull] this ObjectExtensionPropertyInfo propertyExtension) + { + Check.NotNull(propertyExtension, nameof(propertyExtension)); + + return propertyExtension + .Configuration + .ContainsKey(EfCorePropertyConfigurationName); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs new file mode 100644 index 0000000000..4189c87962 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs @@ -0,0 +1,27 @@ +using System; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Volo.Abp.ObjectExtending +{ + public class ObjectExtensionPropertyInfoEfCoreMappingOptions + { + [NotNull] + public ObjectExtensionPropertyInfo ExtensionProperty { get; } + + [NotNull] + public ObjectExtensionInfo ObjectExtension => ExtensionProperty.ObjectExtension; + + [CanBeNull] + public Action PropertyBuildAction { get; set; } + + public ObjectExtensionPropertyInfoEfCoreMappingOptions( + [NotNull] ObjectExtensionPropertyInfo extensionProperty, + [CanBeNull] Action propertyBuildAction = null) + { + ExtensionProperty = Check.NotNull(extensionProperty, nameof(extensionProperty)); + + PropertyBuildAction = propertyBuildAction; + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs index e69d3f67c7..b78a05c684 100644 --- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs +++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs @@ -70,9 +70,17 @@ namespace Volo.Abp.IdentityModel } var tokenResponse = await GetTokenResponse(discoveryResponse, configuration); + if (tokenResponse.IsError) { - throw new AbpException($"Could not get token from the OpenId Connect server! ErrorType: {tokenResponse.ErrorType}. Error: {tokenResponse.Error}. ErrorDescription: {tokenResponse.ErrorDescription}. HttpStatusCode: {tokenResponse.HttpStatusCode}"); + if (tokenResponse.ErrorDescription != null) + { + throw new AbpException($"Could not get token from the OpenId Connect server! ErrorType: {tokenResponse.ErrorType}. Error: {tokenResponse.Error}. ErrorDescription: {tokenResponse.ErrorDescription}. HttpStatusCode: {tokenResponse.HttpStatusCode}"); + } + + var rawError = tokenResponse.Raw; + var withoutInnerException = rawError.Split(new string[] { "" }, StringSplitOptions.RemoveEmptyEntries); + throw new AbpException(withoutInnerException[0]); } return tokenResponse.AccessToken; diff --git a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs index d54e7d82d0..9e0b8f04a7 100644 --- a/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs +++ b/framework/src/Volo.Abp.Ldap/Volo/Abp/Ldap/LdapManager.cs @@ -3,7 +3,9 @@ using Microsoft.Extensions.Options; using Novell.Directory.Ldap; using System.Collections.Generic; using System.Text; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; +using Volo.Abp.ExceptionHandling; using Volo.Abp.Ldap.Exceptions; using Volo.Abp.Ldap.Modeling; @@ -13,6 +15,7 @@ namespace Volo.Abp.Ldap { private readonly string _searchBase; private readonly AbpLdapOptions _ldapOptions; + private readonly IHybridServiceScopeFactory _hybridServiceScopeFactory; private readonly string[] _attributes = { @@ -21,8 +24,9 @@ namespace Volo.Abp.Ldap "sAMAccountName", "userPrincipalName", "telephoneNumber", "mail" }; - public LdapManager(IOptions ldapSettingsOptions) + public LdapManager(IOptions ldapSettingsOptions, IHybridServiceScopeFactory hybridServiceScopeFactory) { + _hybridServiceScopeFactory = hybridServiceScopeFactory; _ldapOptions = ldapSettingsOptions.Value; _searchBase = _ldapOptions.SearchBase; } @@ -231,8 +235,15 @@ namespace Volo.Abp.Ldap return true; } } - catch (Exception ) + catch (Exception ex) { + using (var scope = _hybridServiceScopeFactory.CreateScope()) + { + scope.ServiceProvider + .GetRequiredService() + .NotifyAsync(ex); + } + return false; } } diff --git a/framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xml b/framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xml new file mode 100644 index 0000000000..be0de3a908 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xsd b/framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/Properties/AssemblyInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..fe0b656805 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Volo.Abp.ObjectExtending.Tests")] diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo.Abp.ObjectExtending.csproj b/framework/src/Volo.Abp.ObjectExtending/Volo.Abp.ObjectExtending.csproj new file mode 100644 index 0000000000..43da943f8b --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo.Abp.ObjectExtending.csproj @@ -0,0 +1,21 @@ + + + + + + + netstandard2.0 + Volo.Abp.ObjectExtending + Volo.Abp.ObjectExtending + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/HasExtraPropertiesExtensions.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs similarity index 100% rename from framework/src/Volo.Abp.Data/Volo/Abp/Data/HasExtraPropertiesExtensions.cs rename to framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/IHasExtraProperties.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs similarity index 100% rename from framework/src/Volo.Abp.Data/Volo/Abp/Data/IHasExtraProperties.cs rename to framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/AbpObjectExtendingModule.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/AbpObjectExtendingModule.cs new file mode 100644 index 0000000000..9392429fd3 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/AbpObjectExtendingModule.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Modularity; + +namespace Volo.Abp.ObjectExtending +{ + public class AbpObjectExtendingModule : AbpModule + { + + } +} diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs new file mode 100644 index 0000000000..07243c9dca --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Volo.Abp.Data; + +namespace Volo.Abp.ObjectExtending +{ + [Serializable] + public class ExtensibleObject : IHasExtraProperties + { + public Dictionary ExtraProperties { get; protected set; } + + public ExtensibleObject() + { + ExtraProperties = new Dictionary(); + } + } +} diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions.cs new file mode 100644 index 0000000000..6b687fcf81 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Volo.Abp.Data; + +namespace Volo.Abp.ObjectExtending +{ + public static class HasExtraPropertiesObjectExtendingExtensions + { + /// + /// Copies extra properties from the object + /// to the object. + /// + /// Checks property definitions (over the ) + /// based on the preference. + /// + /// Source class type + /// Destination class type + /// The source object + /// The destination object + /// + /// Controls which properties to map. + /// + public static void MapExtraPropertiesTo( + [NotNull] this TSource source, + [NotNull] TDestination destination, + MappingPropertyDefinitionChecks? definitionChecks = null) + where TSource : IHasExtraProperties + where TDestination : IHasExtraProperties + { + Check.NotNull(source, nameof(source)); + Check.NotNull(destination, nameof(destination)); + + MapExtraPropertiesTo( + typeof(TSource), + typeof(TDestination), + source.ExtraProperties, + destination.ExtraProperties, + definitionChecks + ); + } + + /// + /// Copies extra properties from the object + /// to the object. + /// + /// Checks property definitions (over the ) + /// based on the preference. + /// + /// Source class type (for definition check) + /// Destination class type (for definition check) + /// The source dictionary object + /// The destination dictionary object + /// + /// Controls which properties to map. + /// + public static void MapExtraPropertiesTo( + [NotNull] Dictionary sourceDictionary, + [NotNull] Dictionary destinationDictionary, + MappingPropertyDefinitionChecks? definitionChecks = null) + where TSource : IHasExtraProperties + where TDestination : IHasExtraProperties + { + MapExtraPropertiesTo( + typeof(TSource), + typeof(TDestination), + sourceDictionary, + destinationDictionary, + definitionChecks + ); + } + + /// + /// Copies extra properties from the object + /// to the object. + /// + /// Checks property definitions (over the ) + /// based on the preference. + /// + /// Source type (for definition check) + /// Destination class type (for definition check) + /// The source dictionary object + /// The destination dictionary object + /// + /// Controls which properties to map. + /// + public static void MapExtraPropertiesTo( + [NotNull] Type sourceType, + [NotNull] Type destinationType, + [NotNull] Dictionary sourceDictionary, + [NotNull] Dictionary destinationDictionary, + MappingPropertyDefinitionChecks? definitionChecks = null) + { + Check.AssignableTo(sourceType, nameof(sourceType)); + Check.AssignableTo(destinationType, nameof(destinationType)); + Check.NotNull(sourceDictionary, nameof(sourceDictionary)); + Check.NotNull(destinationDictionary, nameof(destinationDictionary)); + + var sourceObjectExtension = ObjectExtensionManager.Instance.GetOrNull(sourceType); + var destinationObjectExtension = ObjectExtensionManager.Instance.GetOrNull(destinationType); + + foreach (var keyValue in sourceDictionary) + { + if (CanMapProperty( + keyValue.Key, + sourceObjectExtension, + destinationObjectExtension, + definitionChecks)) + { + destinationDictionary[keyValue.Key] = keyValue.Value; + } + } + } + + //TODO: Move these methods to a class like ObjectExtensionHelper + + public static bool CanMapProperty( + [NotNull] string propertyName, + MappingPropertyDefinitionChecks? definitionChecks = null) + { + return CanMapProperty( + typeof(TSource), + typeof(TDestination), + propertyName, + definitionChecks + ); + } + + public static bool CanMapProperty( + [NotNull] Type sourceType, + [NotNull] Type destinationType, + [NotNull] string propertyName, + MappingPropertyDefinitionChecks? definitionChecks = null) + { + Check.AssignableTo(sourceType, nameof(sourceType)); + Check.AssignableTo(destinationType, nameof(destinationType)); + Check.NotNull(propertyName, nameof(propertyName)); + + var sourceObjectExtension = ObjectExtensionManager.Instance.GetOrNull(sourceType); + var destinationObjectExtension = ObjectExtensionManager.Instance.GetOrNull(destinationType); + + return CanMapProperty( + propertyName, + sourceObjectExtension, + destinationObjectExtension, + definitionChecks); + } + + private static bool CanMapProperty( + [NotNull] string propertyName, + [CanBeNull] ObjectExtensionInfo sourceObjectExtension, + [CanBeNull] ObjectExtensionInfo destinationObjectExtension, + MappingPropertyDefinitionChecks? definitionChecks = null) + { + Check.NotNull(propertyName, nameof(propertyName)); + + if (definitionChecks != null) + { + if (definitionChecks.Value.HasFlag(MappingPropertyDefinitionChecks.Source)) + { + if (sourceObjectExtension == null) + { + return false; + } + + if (!sourceObjectExtension.HasProperty(propertyName)) + { + return false; + } + } + + if (definitionChecks.Value.HasFlag(MappingPropertyDefinitionChecks.Destination)) + { + if (destinationObjectExtension == null) + { + return false; + } + + if (!destinationObjectExtension.HasProperty(propertyName)) + { + return false; + } + } + + return true; + } + else + { + var sourcePropertyDefinition = sourceObjectExtension?.GetPropertyOrNull(propertyName); + var destinationPropertyDefinition = destinationObjectExtension?.GetPropertyOrNull(propertyName); + + if (sourcePropertyDefinition != null) + { + if (destinationPropertyDefinition != null) + { + return true; + } + + if (sourcePropertyDefinition.CheckPairDefinitionOnMapping == false) + { + return true; + } + } + else if (destinationPropertyDefinition != null) + { + if (destinationPropertyDefinition.CheckPairDefinitionOnMapping == false) + { + return true; + } + } + + return false; + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs new file mode 100644 index 0000000000..b33638fa7c --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs @@ -0,0 +1,28 @@ +using System; + +namespace Volo.Abp.ObjectExtending +{ + [Flags] + public enum MappingPropertyDefinitionChecks : byte + { + /// + /// No check. Copy all extra properties from the source to the destination. + /// + None = 0, + + /// + /// Copy the extra properties defined for the source class. + /// + Source = 1, + + /// + /// Copy the extra properties defined for the destination class. + /// + Destination = 2, + + /// + /// Copy extra properties defined for both of the source and destination classes. + /// + Both = Source | Destination + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs new file mode 100644 index 0000000000..89ecbab574 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using JetBrains.Annotations; +using Volo.Abp.Data; + +namespace Volo.Abp.ObjectExtending +{ + public class ObjectExtensionInfo + { + [NotNull] + public Type Type { get; } + + [NotNull] + protected Dictionary Properties { get; } + + [NotNull] + public Dictionary Configuration { get; } + + public ObjectExtensionInfo([NotNull] Type type) + { + Type = Check.AssignableTo(type, nameof(type)); + Properties = new Dictionary(); + Configuration = new Dictionary(); + } + + public virtual bool HasProperty(string propertyName) + { + return Properties.ContainsKey(propertyName); + } + + [NotNull] + public virtual ObjectExtensionInfo AddOrUpdateProperty( + [NotNull] string propertyName, + [CanBeNull] Action configureAction = null) + { + return AddOrUpdateProperty( + typeof(TProperty), + propertyName, + configureAction + ); + } + + [NotNull] + public virtual ObjectExtensionInfo AddOrUpdateProperty( + [NotNull] Type propertyType, + [NotNull] string propertyName, + [CanBeNull] Action configureAction = null) + { + Check.NotNull(propertyType, nameof(propertyType)); + Check.NotNull(propertyName, nameof(propertyName)); + + var propertyInfo = Properties.GetOrAdd( + propertyName, + () => new ObjectExtensionPropertyInfo(this, propertyType, propertyName) + ); + + configureAction?.Invoke(propertyInfo); + + return this; + } + + [NotNull] + public virtual ImmutableList GetProperties() + { + return Properties.Values.ToImmutableList(); + } + + [CanBeNull] + public virtual ObjectExtensionPropertyInfo GetPropertyOrNull( + [NotNull] string propertyName) + { + Check.NotNullOrEmpty(propertyName, nameof(propertyName)); + + return Properties.GetOrDefault(propertyName); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs new file mode 100644 index 0000000000..9fd50da492 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using JetBrains.Annotations; +using Volo.Abp.Data; + +namespace Volo.Abp.ObjectExtending +{ + public class ObjectExtensionManager + { + public static ObjectExtensionManager Instance { get; set; } = new ObjectExtensionManager(); + + protected Dictionary ObjectsExtensions { get; } + + protected internal ObjectExtensionManager() + { + ObjectsExtensions = new Dictionary(); + } + + [NotNull] + public virtual ObjectExtensionManager AddOrUpdate( + [CanBeNull] Action configureAction = null) + where TObject : IHasExtraProperties + { + return AddOrUpdate(typeof(TObject), configureAction); + } + + [NotNull] + public virtual ObjectExtensionManager AddOrUpdate( + [NotNull] Type type, + [CanBeNull] Action configureAction = null) + { + Check.AssignableTo(type, nameof(type)); + + var extensionInfo = ObjectsExtensions.GetOrAdd( + type, + () => new ObjectExtensionInfo(type) + ); + + configureAction?.Invoke(extensionInfo); + + return this; + } + + [CanBeNull] + public virtual ObjectExtensionInfo GetOrNull() + where TObject : IHasExtraProperties + { + return GetOrNull(typeof(TObject)); + } + + [CanBeNull] + public virtual ObjectExtensionInfo GetOrNull([NotNull] Type type) + { + Check.AssignableTo(type, nameof(type)); + + return ObjectsExtensions.GetOrDefault(type); + } + + [NotNull] + public virtual ImmutableList GetExtendedObjects() + { + return ObjectsExtensions.Values.ToImmutableList(); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManagerExtensions.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManagerExtensions.cs new file mode 100644 index 0000000000..d21a8c22e1 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManagerExtensions.cs @@ -0,0 +1,44 @@ +using System; +using JetBrains.Annotations; +using Volo.Abp.Data; + +namespace Volo.Abp.ObjectExtending +{ + public static class ObjectExtensionManagerExtensions + { + public static ObjectExtensionManager AddOrUpdateProperty( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] string propertyName, + [CanBeNull] Action configureAction = null) + where TObject : IHasExtraProperties + { + return objectExtensionManager.AddOrUpdateProperty( + typeof(TObject), + typeof(TProperty), + propertyName, + configureAction + ); + } + + public static ObjectExtensionManager AddOrUpdateProperty( + [NotNull] this ObjectExtensionManager objectExtensionManager, + [NotNull] Type objectType, + [NotNull] Type propertyType, + [NotNull] string propertyName, + [CanBeNull] Action configureAction = null) + { + Check.NotNull(objectExtensionManager, nameof(objectExtensionManager)); + + return objectExtensionManager.AddOrUpdate( + objectType, + options => + { + options.AddOrUpdateProperty( + propertyType, + propertyName, + configureAction + ); + }); + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs new file mode 100644 index 0000000000..2edae9a3f8 --- /dev/null +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using JetBrains.Annotations; + +namespace Volo.Abp.ObjectExtending +{ + public class ObjectExtensionPropertyInfo + { + [NotNull] + public ObjectExtensionInfo ObjectExtension { get; } + + [NotNull] + public string Name { get; } + + [NotNull] + public Type Type { get; } + + /// + /// Indicates whether to check the other side of the object mapping + /// if it explicitly defines the property. This property is used in; + /// + /// * .MapExtraPropertiesTo() extension method. + /// * .MapExtraProperties() configuration for the AutoMapper. + /// + /// It this is true, these methods check if the mapping object + /// has defined the property using the . + /// + /// Default: null (unspecified, uses the default logic). + /// + public bool? CheckPairDefinitionOnMapping { get; set; } + + [NotNull] + public Dictionary Configuration { get; } + + public ObjectExtensionPropertyInfo( + [NotNull] ObjectExtensionInfo objectExtension, + [NotNull] Type type, + [NotNull] string name) + { + ObjectExtension = Check.NotNull(objectExtension, nameof(objectExtension)); + Type = Check.NotNull(type, nameof(type)); + Name = Check.NotNull(name, nameof(name)); + + Configuration = new Dictionary(); + } + } +} diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs index 1214a82a99..135156f7a8 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Concurrent; using System.Threading.Tasks; using Volo.Abp.DependencyInjection; +using Volo.Abp.ExceptionHandling; using Volo.Abp.Threading; namespace Volo.Abp.RabbitMQ @@ -17,6 +18,8 @@ namespace Volo.Abp.RabbitMQ protected IConnectionPool ConnectionPool { get; } + protected IExceptionNotifier ExceptionNotifier { get; } + protected AbpTimer Timer { get; } protected ExchangeDeclareConfiguration Exchange { get; private set; } @@ -35,10 +38,12 @@ namespace Volo.Abp.RabbitMQ public RabbitMqMessageConsumer( IConnectionPool connectionPool, - AbpTimer timer) + AbpTimer timer, + IExceptionNotifier exceptionNotifier) { ConnectionPool = connectionPool; Timer = timer; + ExceptionNotifier = exceptionNotifier; Logger = NullLogger.Instance; QueueBindCommands = new ConcurrentQueue(); @@ -114,6 +119,7 @@ namespace Volo.Abp.RabbitMQ catch (Exception ex) { Logger.LogException(ex, LogLevel.Warning); + AsyncHelper.RunSync(() => ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning)); } } @@ -180,6 +186,7 @@ namespace Volo.Abp.RabbitMQ catch (Exception ex) { Logger.LogException(ex, LogLevel.Warning); + AsyncHelper.RunSync(() => ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning)); } } @@ -197,6 +204,7 @@ namespace Volo.Abp.RabbitMQ catch (Exception ex) { Logger.LogException(ex); + await ExceptionNotifier.NotifyAsync(ex); } } @@ -214,6 +222,7 @@ namespace Volo.Abp.RabbitMQ catch (Exception ex) { Logger.LogException(ex, LogLevel.Warning); + AsyncHelper.RunSync(() => ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning)); } } diff --git a/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/AbpTimer.cs b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/AbpTimer.cs index cae8ac984f..a68d15c7a2 100644 --- a/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/AbpTimer.cs +++ b/framework/src/Volo.Abp.Threading/Volo/Abp/Threading/AbpTimer.cs @@ -3,11 +3,12 @@ using System.Threading; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.DependencyInjection; +using Volo.Abp.ExceptionHandling; namespace Volo.Abp.Threading { /// - /// A roboust timer implementation that ensures no overlapping occurs. It waits exactly specified between ticks. + /// A robust timer implementation that ensures no overlapping occurs. It waits exactly specified between ticks. /// public class AbpTimer : ITransientDependency { @@ -29,15 +30,23 @@ namespace Volo.Abp.Threading public ILogger Logger { get; set; } + protected IExceptionNotifier ExceptionNotifier { get; } + private readonly Timer _taskTimer; private volatile bool _performingTasks; private volatile bool _isRunning; - public AbpTimer() + public AbpTimer(IExceptionNotifier exceptionNotifier) { + ExceptionNotifier = exceptionNotifier; Logger = NullLogger.Instance; - _taskTimer = new Timer(TimerCallBack, null, Timeout.Infinite, Timeout.Infinite); + _taskTimer = new Timer( + TimerCallBack, + null, + Timeout.Infinite, + Timeout.Infinite + ); } public void Start(CancellationToken cancellationToken = default) @@ -89,9 +98,10 @@ namespace Volo.Abp.Threading { Elapsed.InvokeSafely(this, new EventArgs()); } - catch + catch(Exception ex) { - + Logger.LogException(ex); + AsyncHelper.RunSync(() => ExceptionNotifier.NotifyAsync(ex)); } finally { diff --git a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs index 5243307116..0de46fbe55 100644 --- a/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs +++ b/framework/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/ExceptionHandling/ExceptionTestController_Tests.cs @@ -1,6 +1,10 @@ using System.Net; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using NSubstitute; using Shouldly; +using Volo.Abp.ExceptionHandling; using Volo.Abp.Http; using Xunit; @@ -8,12 +12,27 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling { public class ExceptionTestController_Tests : AspNetCoreMvcTestBase { + private IExceptionSubscriber _fakeExceptionSubscriber; + + protected override void ConfigureServices(HostBuilderContext context, IServiceCollection services) + { + base.ConfigureServices(context, services); + + _fakeExceptionSubscriber = Substitute.For(); + + services.AddSingleton(_fakeExceptionSubscriber); + } + [Fact] public async Task Should_Return_RemoteServiceErrorResponse_For_UserFriendlyException_For_Void_Return_Value() { var result = await GetResponseAsObjectAsync("/api/exception-test/UserFriendlyException1", HttpStatusCode.Forbidden); result.Error.ShouldNotBeNull(); result.Error.Message.ShouldBe("This is a sample exception!"); + + _fakeExceptionSubscriber + .Received() + .HandleAsync(Arg.Any()); } [Fact] @@ -24,6 +43,10 @@ namespace Volo.Abp.AspNetCore.Mvc.ExceptionHandling "/api/exception-test/UserFriendlyException2" ) ); + + _fakeExceptionSubscriber + .DidNotReceive() + .HandleAsync(Arg.Any()); } } } diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/AutoMapper/AbpAutoMapperExtensibleDtoExtensions_Tests.cs b/framework/test/Volo.Abp.AutoMapper.Tests/AutoMapper/AbpAutoMapperExtensibleDtoExtensions_Tests.cs new file mode 100644 index 0000000000..a55e7d32e3 --- /dev/null +++ b/framework/test/Volo.Abp.AutoMapper.Tests/AutoMapper/AbpAutoMapperExtensibleDtoExtensions_Tests.cs @@ -0,0 +1,41 @@ +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.AutoMapper; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending.TestObjects; +using Volo.Abp.Testing; +using Xunit; + +namespace AutoMapper +{ + public class AbpAutoMapperExtensibleDtoExtensions_Tests : AbpIntegratedTest + { + private readonly Volo.Abp.ObjectMapping.IObjectMapper _objectMapper; + + public AbpAutoMapperExtensibleDtoExtensions_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void MapExtraPropertiesTo_Should_Only_Map_Defined_Properties_By_Default() + { + var person = new ExtensibleTestPerson() + .SetProperty("Name", "John") + .SetProperty("Age", 42) + .SetProperty("ChildCount", 2) + .SetProperty("Sex", "male"); + + var personDto = new ExtensibleTestPersonDto() + .SetProperty("ExistingDtoProperty", "existing-value"); + + _objectMapper.Map(person, personDto); + + personDto.GetProperty("Name").ShouldBe("John"); //Defined in both classes + personDto.HasProperty("Age").ShouldBeFalse(); //Not defined on the destination + personDto.HasProperty("ChildCount").ShouldBeFalse(); //Not defined in the source + personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes + personDto.GetProperty("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values + } + } +} diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj b/framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj index acdd6fac6b..6178700db1 100644 --- a/framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj +++ b/framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperTestModule.cs b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperTestModule.cs index 3d873ccacf..44331522a7 100644 --- a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperTestModule.cs +++ b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperTestModule.cs @@ -1,9 +1,12 @@ -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Modularity; +using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; namespace Volo.Abp.AutoMapper { - [DependsOn(typeof(AbpAutoMapperModule))] + [DependsOn( + typeof(AbpAutoMapperModule), + typeof(AbpObjectExtendingTestModule) + )] public class AutoMapperTestModule : AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyMapProfile.cs b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyMapProfile.cs index a65a7209e2..7fb8dbd2fa 100644 --- a/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyMapProfile.cs +++ b/framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyMapProfile.cs @@ -1,4 +1,5 @@ using AutoMapper; +using Volo.Abp.ObjectExtending.TestObjects; namespace Volo.Abp.AutoMapper.SampleClasses { @@ -7,6 +8,9 @@ namespace Volo.Abp.AutoMapper.SampleClasses public MyMapProfile() { CreateMap().ReverseMap(); + + CreateMap() + .MapExtraProperties(); } } } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs index 71522d0216..e19d939cba 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Autofac; +using Volo.Abp.EntityFrameworkCore.Domain; using Volo.Abp.EntityFrameworkCore.TestApp.SecondContext; using Volo.Abp.EntityFrameworkCore.TestApp.ThirdDbContext; using Volo.Abp.Modularity; @@ -21,6 +22,11 @@ namespace Volo.Abp.EntityFrameworkCore [DependsOn(typeof(AbpEfCoreTestSecondContextModule))] public class AbpEntityFrameworkCoreTestModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + TestEntityExtensionConfigurator.Configure(); + } + public override void ConfigureServices(ServiceConfigurationContext context) { context.Services.AddAbpDbContext(options => diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs index eea827fbee..557116d158 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/ExtraProperties_Tests.cs @@ -1,9 +1,32 @@ -using Volo.Abp.TestApp.Testing; +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.EntityFrameworkCore.Domain { public class ExtraProperties_Tests : ExtraProperties_Tests { + [Fact] + public async Task Should_Get_An_Extra_Property_Configured_As_Extension() + { + var london = await CityRepository.FindByNameAsync("London"); + london.HasProperty("PhoneCode").ShouldBeTrue(); + london.GetProperty("PhoneCode").ShouldBe("42"); + } + [Fact] + public async Task Should_Update_An_Existing_Extra_Property_Configured_As_Extension() + { + var london = await CityRepository.FindByNameAsync("London"); + london.GetProperty("PhoneCode").ShouldBe("42"); + + london.ExtraProperties["PhoneCode"] = "53"; + await CityRepository.UpdateAsync(london); + + var london2 = await CityRepository.FindByNameAsync("London"); + london2.GetProperty("PhoneCode").ShouldBe("53"); + } } } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs new file mode 100644 index 0000000000..3d415d79e6 --- /dev/null +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs @@ -0,0 +1,23 @@ +using Volo.Abp.ObjectExtending; +using Volo.Abp.TestApp.Domain; +using Volo.Abp.Threading; + +namespace Volo.Abp.EntityFrameworkCore.Domain +{ + public static class TestEntityExtensionConfigurator + { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + public static void Configure() + { + OneTimeRunner.Run(() => + { + ObjectExtensionManager.Instance + .MapEfCoreProperty( + "PhoneCode", + p => p.HasMaxLength(8) + ); + }); + } + } +} diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo.Abp.ObjectExtending.Tests.csproj b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo.Abp.ObjectExtending.Tests.csproj new file mode 100644 index 0000000000..0a18e0c44b --- /dev/null +++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo.Abp.ObjectExtending.Tests.csproj @@ -0,0 +1,16 @@ + + + + + + netcoreapp3.1 + + + + + + + + + + diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestBase.cs b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestBase.cs new file mode 100644 index 0000000000..b01ff201c7 --- /dev/null +++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestBase.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Testing; + +namespace Volo.Abp.ObjectExtending +{ + public abstract class AbpObjectExtendingTestBase : AbpIntegratedTest + { + + } +} diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestModule.cs b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestModule.cs new file mode 100644 index 0000000000..470bb547e5 --- /dev/null +++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestModule.cs @@ -0,0 +1,28 @@ +using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending.TestObjects; +using Volo.Abp.Threading; + +namespace Volo.Abp.ObjectExtending +{ + [DependsOn( + typeof(AbpObjectExtendingModule), + typeof(AbpTestBaseModule) + )] + public class AbpObjectExtendingTestModule : AbpModule + { + private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); + + public override void PreConfigureServices(ServiceConfigurationContext context) + { + OneTimeRunner.Run(() => + { + ObjectExtensionManager.Instance + .AddOrUpdateProperty("Name") + .AddOrUpdateProperty("Age") + .AddOrUpdateProperty("NoPairCheck", options => options.CheckPairDefinitionOnMapping = false) + .AddOrUpdateProperty("Name") + .AddOrUpdateProperty("ChildCount"); + }); + } + } +} diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions_Tests.cs b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions_Tests.cs new file mode 100644 index 0000000000..7ba213b81c --- /dev/null +++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions_Tests.cs @@ -0,0 +1,75 @@ +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending.TestObjects; +using Xunit; + +namespace Volo.Abp.ObjectExtending +{ + public class HasExtraPropertiesObjectExtendingExtensions_Tests : AbpObjectExtendingTestBase + { + private readonly ExtensibleTestPerson _person; + private readonly ExtensibleTestPersonDto _personDto; + + public HasExtraPropertiesObjectExtendingExtensions_Tests() + { + _person = new ExtensibleTestPerson() + .SetProperty("Name", "John") + .SetProperty("Age", 42) + .SetProperty("ChildCount", 2) + .SetProperty("Sex", "male") + .SetProperty("NoPairCheck", "test-value"); + + _personDto = new ExtensibleTestPersonDto() + .SetProperty("ExistingDtoProperty", "existing-value"); + } + + [Fact] + public void MapExtraPropertiesTo_Should_Only_Map_Defined_Properties_By_Default() + { + _person.MapExtraPropertiesTo(_personDto); + + _personDto.GetProperty("Name").ShouldBe("John"); //Defined in both classes + _personDto.HasProperty("Age").ShouldBeFalse(); //Not defined on the destination + _personDto.HasProperty("ChildCount").ShouldBeFalse(); //Not defined in the source + _personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes + _personDto.GetProperty("NoPairCheck").ShouldBe("test-value"); //CheckPairDefinitionOnMapping = false + _personDto.GetProperty("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values + } + + [Fact] + public void MapExtraPropertiesTo_Should_Only_Map_Source_Defined_Properties_If_Requested() + { + _person.MapExtraPropertiesTo(_personDto, MappingPropertyDefinitionChecks.Source); + + _personDto.GetProperty("Name").ShouldBe("John"); //Defined in both classes + _personDto.GetProperty("Age").ShouldBe(42); //Defined in source + _personDto.HasProperty("ChildCount").ShouldBeFalse(); //Not defined in the source + _personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes + _personDto.GetProperty("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values + } + + [Fact] + public void MapExtraPropertiesTo_Should_Only_Map_Destination_Defined_Properties_If_Requested() + { + _person.MapExtraPropertiesTo(_personDto, MappingPropertyDefinitionChecks.Destination); + + _personDto.GetProperty("Name").ShouldBe("John"); //Defined in both classes + _personDto.GetProperty("ChildCount").ShouldBe(2); //Defined in destination + _personDto.HasProperty("Age").ShouldBeFalse(); //Not defined in destination + _personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes + _personDto.GetProperty("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values + } + + [Fact] + public void MapExtraPropertiesTo_Should_Copy_all_With_No_Property_Definition_Check() + { + _person.MapExtraPropertiesTo(_personDto, MappingPropertyDefinitionChecks.None); + + _personDto.GetProperty("Name").ShouldBe("John"); + _personDto.GetProperty("Age").ShouldBe(42); + _personDto.GetProperty("ChildCount").ShouldBe(2); + _personDto.GetProperty("Sex").ShouldBe("male"); + _personDto.GetProperty("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values + } + } +} diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ObjectExtensionManager_Tests.cs b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ObjectExtensionManager_Tests.cs new file mode 100644 index 0000000000..857e2fd155 --- /dev/null +++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ObjectExtensionManager_Tests.cs @@ -0,0 +1,63 @@ +using System.Linq; +using Shouldly; +using Xunit; + +namespace Volo.Abp.ObjectExtending +{ + public class ObjectExtensionManager_Tests + { + private readonly ObjectExtensionManager _objectExtensionManager; + + public ObjectExtensionManager_Tests() + { + _objectExtensionManager = new ObjectExtensionManager(); + } + + [Fact] + public void Should_Not_Add_Same_Property_Multiple_Times() + { + _objectExtensionManager + .AddOrUpdateProperty("TestProp") + .AddOrUpdateProperty("TestProp"); + + var objectExtension = _objectExtensionManager.GetOrNull(); + objectExtension.ShouldNotBeNull(); + + var properties = objectExtension.GetProperties(); + properties.Count.ShouldBe(1); + properties.FirstOrDefault(p => p.Name == "TestProp").ShouldNotBeNull(); + } + + [Fact] + public void Should_Update_Property_Configuration() + { + _objectExtensionManager + .AddOrUpdateProperty( + "TestProp", + options => + { + options.Configuration["TestConfig1"] = "TestConfig1-Value"; + } + ).AddOrUpdateProperty( + "TestProp", + options => + { + options.Configuration["TestConfig2"] = "TestConfig2-Value"; + } + ); + + var objectExtension = _objectExtensionManager.GetOrNull(); + objectExtension.ShouldNotBeNull(); + + var property = objectExtension.GetPropertyOrNull("TestProp"); + property.ShouldNotBeNull(); + property.Configuration["TestConfig1"].ShouldBe("TestConfig1-Value"); + property.Configuration["TestConfig2"].ShouldBe("TestConfig2-Value"); + } + + private class MyExtensibleObject : ExtensibleObject + { + + } + } +} diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPerson.cs b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPerson.cs new file mode 100644 index 0000000000..bd24209212 --- /dev/null +++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPerson.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.ObjectExtending.TestObjects +{ + public class ExtensibleTestPerson : ExtensibleObject + { + + } +} diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPersonDto.cs b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPersonDto.cs new file mode 100644 index 0000000000..62f0f94866 --- /dev/null +++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPersonDto.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.ObjectExtending.TestObjects +{ + public class ExtensibleTestPersonDto : ExtensibleObject + { + + } +} diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs index 03e4ed5465..312125d541 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/TestDataBuilder.cs @@ -21,7 +21,7 @@ namespace Volo.Abp.TestApp private readonly IRepository _entityWithIntPksRepository; public TestDataBuilder( - IBasicRepository personRepository, + IBasicRepository personRepository, ICityRepository cityRepository, IRepository entityWithIntPksRepository) { @@ -46,7 +46,7 @@ namespace Volo.Abp.TestApp await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Tokyo")); await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Madrid")); - await _cityRepository.InsertAsync(new City(LondonCityId, "London") { ExtraProperties = { { "Population", 10_470_000 } } }); + await _cityRepository.InsertAsync(new City(LondonCityId, "London") { ExtraProperties = { { "Population", 10_470_000 }, { "PhoneCode", "42" } } }); await _cityRepository.InsertAsync(istanbul); await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Paris")); await _cityRepository.InsertAsync(new City(Guid.NewGuid(), "Washington")); diff --git a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/ExtraProperties_Tests.cs b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/ExtraProperties_Tests.cs index 67eff13572..47f4c6a4cd 100644 --- a/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/ExtraProperties_Tests.cs +++ b/framework/test/Volo.Abp.TestApp/Volo/Abp/TestApp/Testing/ExtraProperties_Tests.cs @@ -41,6 +41,7 @@ namespace Volo.Abp.TestApp.Testing public async Task Should_Update_An_Existing_Extra_Property() { var london = await CityRepository.FindByNameAsync("London"); + london.GetProperty("Population").ShouldBe(10_470_000); london.ExtraProperties["Population"] = 11_000_042; await CityRepository.UpdateAsync(london); diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs index fb676fe288..9efc4ae0f6 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Net; using System.Threading; using System.Threading.Tasks; +using Volo.Abp.Auditing; using Volo.Abp.Domain.Repositories; namespace Volo.Abp.AuditLogging @@ -44,5 +45,29 @@ namespace Volo.Abp.AuditLogging Task> GetAverageExecutionDurationPerDayAsync( DateTime startDate, DateTime endDate); + + Task GetEntityChange(Guid auditLogId, Guid entityChangeId, bool includeDetails = true); + + Task> GetEntityChangeListAsync( + string sorting = null, + int maxResultCount = 50, + int skipCount = 0, + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + bool includeDetails = false, + CancellationToken cancellationToken = default); + + Task GetEntityChangeCountAsync( + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + CancellationToken cancellationToken = default); } } diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/AbpAuditLoggingEfCoreQueryableExtensions.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/AbpAuditLoggingEfCoreQueryableExtensions.cs index 27445045e7..6dc4bfadb8 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/AbpAuditLoggingEfCoreQueryableExtensions.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/AbpAuditLoggingEfCoreQueryableExtensions.cs @@ -18,5 +18,17 @@ namespace Volo.Abp.AuditLogging .Include(x => x.Actions) .Include(x => x.EntityChanges).ThenInclude(ec=>ec.PropertyChanges); } + + public static IQueryable IncludeDetails( + this IQueryable queryable, + bool include = true) + { + if (!include) + { + return queryable; + } + + return queryable.Include(x => x.PropertyChanges); + } } } diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingDbContextModelBuilderExtensions.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingDbContextModelBuilderExtensions.cs index 23b4cb3dbe..619eed057c 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingDbContextModelBuilderExtensions.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingDbContextModelBuilderExtensions.cs @@ -25,7 +25,7 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore { b.ToTable(options.TablePrefix + "AuditLogs", options.Schema); - b.ConfigureExtraProperties(); + b.ConfigureByConvention(); b.Property(x => x.ApplicationName).HasMaxLength(AuditLogConsts.MaxApplicationNameLength).HasColumnName(nameof(AuditLog.ApplicationName)); b.Property(x => x.ClientIpAddress).HasMaxLength(AuditLogConsts.MaxClientIpAddressLength).HasColumnName(nameof(AuditLog.ClientIpAddress)); @@ -56,7 +56,7 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore { b.ToTable(options.TablePrefix + "AuditLogActions", options.Schema); - b.ConfigureExtraProperties(); + b.ConfigureByConvention(); b.Property(x => x.AuditLogId).HasColumnName(nameof(AuditLogAction.AuditLogId)); b.Property(x => x.ServiceName).HasMaxLength(AuditLogActionConsts.MaxServiceNameLength).HasColumnName(nameof(AuditLogAction.ServiceName)); @@ -73,7 +73,7 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore { b.ToTable(options.TablePrefix + "EntityChanges", options.Schema); - b.ConfigureExtraProperties(); + b.ConfigureByConvention(); b.Property(x => x.EntityTypeFullName).HasMaxLength(EntityChangeConsts.MaxEntityTypeFullNameLength).IsRequired().HasColumnName(nameof(EntityChange.EntityTypeFullName)); b.Property(x => x.EntityId).HasMaxLength(EntityChangeConsts.MaxEntityIdLength).IsRequired().HasColumnName(nameof(EntityChange.EntityId)); @@ -92,6 +92,8 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore { b.ToTable(options.TablePrefix + "EntityPropertyChanges", options.Schema); + b.ConfigureByConvention(); + b.Property(x => x.NewValue).HasMaxLength(EntityPropertyChangeConsts.MaxNewValueLength).HasColumnName(nameof(EntityPropertyChange.NewValue)); b.Property(x => x.PropertyName).HasMaxLength(EntityPropertyChangeConsts.MaxPropertyNameLength).IsRequired().HasColumnName(nameof(EntityPropertyChange.PropertyName)); b.Property(x => x.PropertyTypeFullName).HasMaxLength(EntityPropertyChangeConsts.MaxPropertyTypeFullNameLength).IsRequired().HasColumnName(nameof(EntityPropertyChange.PropertyTypeFullName)); diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs index 1fabbcbe7c..b6308c5741 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs @@ -6,6 +6,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Volo.Abp.Auditing; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; @@ -134,10 +135,71 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore return result.ToDictionary(element => element.Day.ClearTime(), element => element.avgExecutionTime); } - + public override IQueryable WithDetails() { return GetQueryable().IncludeDetails(); } + + public Task GetEntityChange(Guid auditLogId, Guid entityChangeId, bool includeDetails = true) + { + return DbContext.Set().AsNoTracking().IncludeDetails(includeDetails) + .Where(x => x.Id == entityChangeId && x.AuditLogId == auditLogId).FirstAsync(); + } + + public virtual async Task> GetEntityChangeListAsync( + string sorting = null, + int maxResultCount = 50, + int skipCount = 0, + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var query = GetEntityChangeListQuery(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName, includeDetails); + + return await query.OrderBy(sorting ?? "changeTime desc") + .PageBy(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task GetEntityChangeCountAsync( + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + CancellationToken cancellationToken = default) + { + var query = GetEntityChangeListQuery(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName); + + var totalCount = await query.LongCountAsync(GetCancellationToken(cancellationToken)); + + return totalCount; + } + + protected virtual IQueryable GetEntityChangeListQuery( + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + bool includeDetails = false) + { + return DbContext.Set().AsNoTracking().IncludeDetails(includeDetails) + .WhereIf(auditLogId.HasValue, e => e.AuditLogId == auditLogId) + .WhereIf(startTime.HasValue, e => e.ChangeTime >= startTime) + .WhereIf(endTime.HasValue, e => e.ChangeTime <= endTime) + .WhereIf(changeType.HasValue, e => e.ChangeType == changeType) + .WhereIf(!string.IsNullOrWhiteSpace(entityId), e => e.EntityId == entityId) + .WhereIf(!string.IsNullOrWhiteSpace(entityTypeFullName), + e => e.EntityTypeFullName.Contains(entityTypeFullName)); + } } } diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs index fdb9bca387..89ed33ee5f 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; using MongoDB.Driver.Linq; +using Volo.Abp.Auditing; using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.MongoDB; @@ -138,5 +139,116 @@ namespace Volo.Abp.AuditLogging.MongoDB return result.ToDictionary(element => element.Day.ClearTime(), element => element.avgExecutionTime); } + + public virtual async Task GetEntityChange(Guid auditLogId, Guid entityChangeId, bool includeDetails = true) + { + return (await GetMongoQueryable() + .Where(x => x.Id == auditLogId && x.EntityChanges.Any(y => y.Id == entityChangeId)).FirstAsync()) + .EntityChanges.First(x => x.Id == entityChangeId); + } + + public virtual async Task> GetEntityChangeListAsync( + string sorting = null, + int maxResultCount = 50, + int skipCount = 0, + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var query = GetEntityChangeListQuery(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName); + + var auditLogs = await query.As>() + .PageBy>(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + + // TODO: Improve this specification + + return auditLogs + .SelectMany(x => x.EntityChanges.Where(y => + IsSatisfiedEntityChange(y, auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName))) + .AsQueryable().OrderBy(sorting ?? "changeTime desc").ToList(); + } + + public virtual async Task GetEntityChangeCountAsync( + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + CancellationToken cancellationToken = default) + { + var query = GetEntityChangeListQuery(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName); + var count = await query.As>() + .LongCountAsync(GetCancellationToken(cancellationToken)); + + return count; + } + + protected virtual IQueryable GetEntityChangeListQuery( + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null) + { + return GetMongoQueryable() + .Where(x => x.EntityChanges != null) + .WhereIf(auditLogId.HasValue, e => e.Id == auditLogId) + .WhereIf(startTime.HasValue, e => e.EntityChanges.Any(ec => ec.ChangeTime >= startTime)) + .WhereIf(endTime.HasValue, e => e.EntityChanges.Any(ec => ec.ChangeTime >= endTime)) + .WhereIf(changeType.HasValue, e => e.EntityChanges.Any(ec => ec.ChangeType == changeType)) + .WhereIf(!string.IsNullOrWhiteSpace(entityId), e => e.EntityChanges.Any(ec => ec.EntityId == entityId)) + .WhereIf(!string.IsNullOrWhiteSpace(entityTypeFullName), + e => e.EntityChanges.Any(ec => ec.EntityTypeFullName.Contains(entityTypeFullName))); + } + + protected virtual bool IsSatisfiedEntityChange( + EntityChange entityChange, + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null) + { + if (auditLogId != null && auditLogId != entityChange.AuditLogId) + { + return false; + } + + if (startTime != null && startTime.Value >= entityChange.ChangeTime) + { + return false; + } + + if (endTime != null && endTime.Value <= entityChange.ChangeTime) + { + return false; + } + + if (changeType != null && changeType != entityChange.ChangeType) + { + return false; + } + + if (entityId != null && entityId != entityChange.EntityId) + { + return false; + } + + if (entityTypeFullName != null && entityChange.EntityTypeFullName.Contains(entityTypeFullName)) + { + return false; + } + + return true; + } } } diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogRepository_Tests.cs b/modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogRepository_Tests.cs index f88c10c079..78abe1862d 100644 --- a/modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogRepository_Tests.cs +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogRepository_Tests.cs @@ -333,5 +333,433 @@ namespace Volo.Abp.AuditLogging results.Count.ShouldBe(1); results.Values.First().ShouldBe(50); // (45 + 55) / 2 } + + [Fact] + public async Task GetEntityChangeListAsync() + { + // Arrange + var userId = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var userId2 = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var ipAddress = "153.1.7.61"; + var firstComment = "first Comment"; + + var log1 = new AuditLogInfo + { + UserId = userId, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + UserName = "Douglas", + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Deleted", + ChangeType = EntityChangeType.Deleted, + ChangeTime = DateTime.Now, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + }, + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Created", + ChangeType = EntityChangeType.Created, + ChangeTime = DateTime.Now, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + + } + }; + + var log2 = new AuditLogInfo + { + UserId = userId2, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + HttpStatusCode = (int?)HttpStatusCode.BadGateway, + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Updated", + ChangeType = EntityChangeType.Updated, + ChangeTime = DateTime.Now, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + } + }; + + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); + + //Assert + var entityChanges = await AuditLogRepository.GetEntityChangeListAsync(); + entityChanges.ShouldNotBeNull(); + entityChanges.Count.ShouldBe(3); + + entityChanges.Single(x => x.ChangeType == EntityChangeType.Created).ShouldNotBeNull(); + entityChanges.Single(x => x.ChangeType == EntityChangeType.Deleted).ShouldNotBeNull(); + entityChanges.Single(x => x.ChangeType == EntityChangeType.Updated).ShouldNotBeNull(); + } + + [Fact] + public async Task GetEntityChangeAsync() + { + // Arrange + var userId = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var userId2 = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var ipAddress = "153.1.7.61"; + var firstComment = "first Comment"; + + var log1 = new AuditLogInfo + { + UserId = userId, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + UserName = "Douglas", + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Deleted", + ChangeType = EntityChangeType.Deleted, + ChangeTime = new DateTime(1995, 3, 27), + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + }, + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Created", + ChangeType = EntityChangeType.Created, + ChangeTime = DateTime.Now, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + + } + }; + + var log2 = new AuditLogInfo + { + UserId = userId2, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + HttpStatusCode = (int?)HttpStatusCode.BadGateway, + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Updated", + ChangeType = EntityChangeType.Updated, + ChangeTime = DateTime.Now, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + } + }; + + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); + + var entityChanges = await AuditLogRepository.GetEntityChangeListAsync(); + var entityChange = + await AuditLogRepository.GetEntityChange(entityChanges.First().AuditLogId, entityChanges.First().Id); + + entityChange.ChangeTime.ShouldBe(entityChanges.First().ChangeTime); + } + + [Fact] + public async Task GetOrderedEntityChangeListAsync() + { + // Arrange + var userId = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var userId2 = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var ipAddress = "153.1.7.61"; + var firstComment = "first Comment"; + + var deletedEntityChangeTime = new DateTime(2000, 05, 05, 05, 05, 05); + var createdEntityChangeTime = new DateTime(2005, 05, 05, 05, 05, 05); + var updatedEntityChangeTime = new DateTime(2010, 05, 05, 05, 05, 05); + + var log1 = new AuditLogInfo + { + UserId = userId, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + UserName = "Douglas", + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Deleted", + ChangeType = EntityChangeType.Deleted, + ChangeTime = deletedEntityChangeTime, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + }, + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Created", + ChangeType = EntityChangeType.Created, + ChangeTime = createdEntityChangeTime, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + + } + }; + + var log2 = new AuditLogInfo + { + UserId = userId2, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + HttpStatusCode = (int?)HttpStatusCode.BadGateway, + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Updated", + ChangeType = EntityChangeType.Updated, + ChangeTime = updatedEntityChangeTime, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + } + }; + + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); + + //Assert + var entityChangesDesc = await AuditLogRepository.GetEntityChangeListAsync(); + entityChangesDesc.ShouldNotBeNull(); + entityChangesDesc.Count.ShouldBe(3); + + entityChangesDesc.First().EntityTypeFullName.ShouldBe("Volo.Abp.AuditLogging.TestEntity_Updated"); + entityChangesDesc.Last().EntityTypeFullName.ShouldBe("Volo.Abp.AuditLogging.TestEntity_Deleted"); + + var entityChangesAsc = await AuditLogRepository.GetEntityChangeListAsync("changeTime asc"); + + entityChangesAsc.First().EntityTypeFullName.ShouldBe("Volo.Abp.AuditLogging.TestEntity_Deleted"); + entityChangesAsc.Last().EntityTypeFullName.ShouldBe("Volo.Abp.AuditLogging.TestEntity_Updated"); + } + + [Fact] + public async Task GetSpecifiedEntityChangeListAsync() + { + // Arrange + var userId = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var userId2 = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var ipAddress = "153.1.7.61"; + var firstComment = "first Comment"; + + var deletedEntityChangeTime = new DateTime(2000, 05, 05, 05, 05, 05); + var createdEntityChangeTime = new DateTime(2005, 05, 05, 05, 05, 05); + var updatedEntityChangeTime = new DateTime(2010, 05, 05, 05, 05, 05); + + var log1 = new AuditLogInfo + { + UserId = userId, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + UserName = "Douglas", + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Deleted", + ChangeType = EntityChangeType.Deleted, + ChangeTime = deletedEntityChangeTime, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + }, + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Created", + ChangeType = EntityChangeType.Created, + ChangeTime = createdEntityChangeTime, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + } + }; + + var log2 = new AuditLogInfo + { + UserId = userId2, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + HttpStatusCode = (int?)HttpStatusCode.BadGateway, + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Updated", + ChangeType = EntityChangeType.Updated, + ChangeTime = updatedEntityChangeTime, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + } + }; + + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); + + //Assert + var entityChanges = await AuditLogRepository.GetEntityChangeListAsync(changeType: EntityChangeType.Created); + entityChanges.ShouldNotBeNull(); + entityChanges.Count.ShouldBe(1); + } } } diff --git a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/BackgroundJobsDbContextModelCreatingExtensions.cs b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/BackgroundJobsDbContextModelCreatingExtensions.cs index 8a3e4b126a..0d16130fb5 100644 --- a/modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/BackgroundJobsDbContextModelCreatingExtensions.cs +++ b/modules/background-jobs/src/Volo.Abp.BackgroundJobs.EntityFrameworkCore/Volo/Abp/BackgroundJobs/EntityFrameworkCore/BackgroundJobsDbContextModelCreatingExtensions.cs @@ -23,8 +23,7 @@ namespace Volo.Abp.BackgroundJobs.EntityFrameworkCore { b.ToTable(options.TablePrefix + "BackgroundJobs", options.Schema); - b.ConfigureCreationTime(); - b.ConfigureExtraProperties(); + b.ConfigureByConvention(); b.Property(x => x.JobName).IsRequired().HasMaxLength(BackgroundJobRecordConsts.MaxJobNameLength); b.Property(x => x.JobArgs).IsRequired().HasMaxLength(BackgroundJobRecordConsts.MaxJobArgsLength); diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200402110128_Added_Post_Description.Designer.cs b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200402110128_Added_Post_Description.Designer.cs new file mode 100644 index 0000000000..f30670479d --- /dev/null +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200402110128_Added_Post_Description.Designer.cs @@ -0,0 +1,926 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Volo.BloggingTestApp.EntityFrameworkCore; + +namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations +{ + [DbContext(typeof(BloggingTestAppDbContext))] + [Migration("20200402110128_Added_Post_Description")] + partial class Added_Post_Description + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.2") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Description") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Regex") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("RegexDescription") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("Required") + .HasColumnType("bit"); + + b.Property("ValueType") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AbpClaimTypes"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnName("IsDefault") + .HasColumnType("bit"); + + b.Property("IsPublic") + .HasColumnName("IsPublic") + .HasColumnType("bit"); + + b.Property("IsStatic") + .HasColumnName("IsStatic") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedName") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName"); + + b.ToTable("AbpRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AbpRoleClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AccessFailedCount") + .ValueGeneratedOnAdd() + .HasColumnName("AccessFailedCount") + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("LockoutEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("LockoutEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("NormalizedEmail") + .IsRequired() + .HasColumnName("NormalizedEmail") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .IsRequired() + .HasColumnName("NormalizedUserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PasswordHash") + .HasColumnName("PasswordHash") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("SecurityStamp") + .IsRequired() + .HasColumnName("SecurityStamp") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("TwoFactorEnabled") + .ValueGeneratedOnAdd() + .HasColumnName("TwoFactorEnabled") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("Email"); + + b.HasIndex("NormalizedEmail"); + + b.HasIndex("NormalizedUserName"); + + b.HasIndex("UserName"); + + b.ToTable("AbpUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .HasColumnType("uniqueidentifier"); + + b.Property("ClaimType") + .IsRequired() + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AbpUserClaims"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(196)") + .HasMaxLength(196); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "LoginProvider"); + + b.HasIndex("LoginProvider", "ProviderKey"); + + b.ToTable("AbpUserLogins"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId", "UserId"); + + b.ToTable("AbpUserRoles"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uniqueidentifier"); + + b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Name") + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("Value") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AbpUserTokens"); + }); + + modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .IsRequired() + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpPermissionGrants"); + }); + + modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(128)") + .HasMaxLength(128); + + b.Property("ProviderKey") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("ProviderName") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(2048)") + .HasMaxLength(2048); + + b.HasKey("Id"); + + b.HasIndex("Name", "ProviderName", "ProviderKey"); + + b.ToTable("AbpSettings"); + }); + + modelBuilder.Entity("Volo.Blogging.Blogs.Blog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnName("Name") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("ShortName") + .IsRequired() + .HasColumnName("ShortName") + .HasColumnType("nvarchar(32)") + .HasMaxLength(32); + + b.HasKey("Id"); + + b.ToTable("BlgBlogs"); + }); + + modelBuilder.Entity("Volo.Blogging.Comments.Comment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("PostId") + .HasColumnName("PostId") + .HasColumnType("uniqueidentifier"); + + b.Property("RepliedCommentId") + .HasColumnName("RepliedCommentId") + .HasColumnType("uniqueidentifier"); + + b.Property("Text") + .IsRequired() + .HasColumnName("Text") + .HasColumnType("nvarchar(1024)") + .HasMaxLength(1024); + + b.HasKey("Id"); + + b.HasIndex("PostId"); + + b.HasIndex("RepliedCommentId"); + + b.ToTable("BlgComments"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.Post", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BlogId") + .HasColumnName("BlogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Content") + .HasColumnName("Content") + .HasColumnType("nvarchar(max)") + .HasMaxLength(1048576); + + b.Property("CoverImage") + .IsRequired() + .HasColumnName("CoverImage") + .HasColumnType("nvarchar(max)"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("ReadCount") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnName("Title") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("Url") + .IsRequired() + .HasColumnName("Url") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.HasKey("Id"); + + b.HasIndex("BlogId"); + + b.ToTable("BlgPosts"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b => + { + b.Property("PostId") + .HasColumnName("PostId") + .HasColumnType("uniqueidentifier"); + + b.Property("TagId") + .HasColumnName("TagId") + .HasColumnType("uniqueidentifier"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("PostId", "TagId"); + + b.HasIndex("TagId"); + + b.ToTable("BlgPostTags"); + }); + + modelBuilder.Entity("Volo.Blogging.Tagging.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("BlogId") + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); + + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); + + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(512)") + .HasMaxLength(512); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); + + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); + + b.Property("Name") + .IsRequired() + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("UsageCount") + .HasColumnName("UsageCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("BlgTags"); + }); + + modelBuilder.Entity("Volo.Blogging.Users.BlogUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .IsRequired() + .HasColumnName("Email") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.Property("EmailConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("EmailConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnName("Name") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("PhoneNumber") + .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") + .HasMaxLength(16); + + b.Property("PhoneNumberConfirmed") + .ValueGeneratedOnAdd() + .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Surname") + .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") + .HasMaxLength(64); + + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); + + b.Property("UserName") + .IsRequired() + .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.ToTable("BlgUsers"); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany("Claims") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Claims") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Logins") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => + { + b.HasOne("Volo.Abp.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Roles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => + { + b.HasOne("Volo.Abp.Identity.IdentityUser", null) + .WithMany("Tokens") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Blogging.Comments.Comment", b => + { + b.HasOne("Volo.Blogging.Posts.Post", null) + .WithMany() + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Blogging.Comments.Comment", null) + .WithMany() + .HasForeignKey("RepliedCommentId"); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.Post", b => + { + b.HasOne("Volo.Blogging.Blogs.Blog", null) + .WithMany() + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b => + { + b.HasOne("Volo.Blogging.Posts.Post", null) + .WithMany("Tags") + .HasForeignKey("PostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Volo.Blogging.Tagging.Tag", null) + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200402110128_Added_Post_Description.cs b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200402110128_Added_Post_Description.cs new file mode 100644 index 0000000000..01f244be5a --- /dev/null +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20200402110128_Added_Post_Description.cs @@ -0,0 +1,138 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations +{ + public partial class Added_Post_Description : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Email", + table: "BlgUsers", + maxLength: 256, + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 256, + oldNullable: true); + + migrationBuilder.AddColumn( + name: "Description", + table: "BlgPosts", + maxLength: 1000, + nullable: true); + + migrationBuilder.AlterColumn( + name: "NormalizedEmail", + table: "AbpUsers", + maxLength: 256, + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 256, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "IsDeleted", + table: "AbpUsers", + nullable: false, + defaultValue: false, + oldClrType: typeof(bool)); + + migrationBuilder.AlterColumn( + name: "Email", + table: "AbpUsers", + maxLength: 256, + nullable: false, + oldClrType: typeof(string), + oldMaxLength: 256, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpUsers", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 256); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpRoles", + maxLength: 256, + nullable: false, + oldClrType: typeof(string), + oldNullable: true); + + migrationBuilder.AddColumn( + name: "ConcurrencyStamp", + table: "AbpClaimTypes", + maxLength: 256, + nullable: false, + defaultValue: ""); + + migrationBuilder.AddColumn( + name: "ExtraProperties", + table: "AbpClaimTypes", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Description", + table: "BlgPosts"); + + migrationBuilder.DropColumn( + name: "ConcurrencyStamp", + table: "AbpClaimTypes"); + + migrationBuilder.DropColumn( + name: "ExtraProperties", + table: "AbpClaimTypes"); + + migrationBuilder.AlterColumn( + name: "Email", + table: "BlgUsers", + maxLength: 256, + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 256); + + migrationBuilder.AlterColumn( + name: "NormalizedEmail", + table: "AbpUsers", + maxLength: 256, + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 256); + + migrationBuilder.AlterColumn( + name: "IsDeleted", + table: "AbpUsers", + nullable: false, + oldClrType: typeof(bool), + oldDefaultValue: false); + + migrationBuilder.AlterColumn( + name: "Email", + table: "AbpUsers", + maxLength: 256, + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 256); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpUsers", + maxLength: 256, + nullable: false, + oldClrType: typeof(string), + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ConcurrencyStamp", + table: "AbpRoles", + nullable: true, + oldClrType: typeof(string), + oldMaxLength: 256); + } + } +} diff --git a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs index fda99109a6..2b0641a680 100644 --- a/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs +++ b/modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs @@ -15,33 +15,52 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.1-rtm-30846") + .HasAnnotation("ProductVersion", "3.1.2") .HasAnnotation("Relational:MaxIdentifierLength", 128) .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); modelBuilder.Entity("Volo.Abp.Identity.IdentityClaimType", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); b.Property("Description") + .HasColumnType("nvarchar(256)") .HasMaxLength(256); - b.Property("IsStatic"); + b.Property("ExtraProperties") + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); + + b.Property("IsStatic") + .HasColumnType("bit"); b.Property("Name") .IsRequired() + .HasColumnType("nvarchar(256)") .HasMaxLength(256); b.Property("Regex") + .HasColumnType("nvarchar(512)") .HasMaxLength(512); b.Property("RegexDescription") + .HasColumnType("nvarchar(128)") .HasMaxLength(128); - b.Property("Required"); + b.Property("Required") + .HasColumnType("bit"); - b.Property("ValueType"); + b.Property("ValueType") + .HasColumnType("int"); b.HasKey("Id"); @@ -51,31 +70,45 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); - b.Property("ConcurrencyStamp"); + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .IsRequired() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(256)") + .HasMaxLength(256); b.Property("ExtraProperties") - .HasColumnName("ExtraProperties"); + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); b.Property("IsDefault") - .HasColumnName("IsDefault"); + .HasColumnName("IsDefault") + .HasColumnType("bit"); b.Property("IsPublic") - .HasColumnName("IsPublic"); + .HasColumnName("IsPublic") + .HasColumnType("bit"); b.Property("IsStatic") - .HasColumnName("IsStatic"); + .HasColumnName("IsStatic") + .HasColumnType("bit"); b.Property("Name") .IsRequired() + .HasColumnType("nvarchar(256)") .HasMaxLength(256); b.Property("NormalizedName") .IsRequired() + .HasColumnType("nvarchar(256)") .HasMaxLength(256); - b.Property("TenantId"); + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); b.HasKey("Id"); @@ -87,18 +120,23 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .HasColumnType("uniqueidentifier"); b.Property("ClaimType") .IsRequired() + .HasColumnType("nvarchar(256)") .HasMaxLength(256); b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") .HasMaxLength(1024); - b.Property("RoleId"); + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); - b.Property("TenantId"); + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); b.HasKey("Id"); @@ -110,97 +148,133 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); b.Property("AccessFailedCount") .ValueGeneratedOnAdd() .HasColumnName("AccessFailedCount") + .HasColumnType("int") .HasDefaultValue(0); b.Property("ConcurrencyStamp") - .IsRequired() + .IsConcurrencyToken() .HasColumnName("ConcurrencyStamp") - .HasMaxLength(256); + .HasColumnType("nvarchar(max)"); - b.Property("CreationTime"); + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); - b.Property("CreatorId"); + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); - b.Property("DeleterId"); + b.Property("DeleterId") + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); - b.Property("DeletionTime"); + b.Property("DeletionTime") + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); b.Property("Email") + .IsRequired() .HasColumnName("Email") + .HasColumnType("nvarchar(256)") .HasMaxLength(256); b.Property("EmailConfirmed") .ValueGeneratedOnAdd() .HasColumnName("EmailConfirmed") + .HasColumnType("bit") .HasDefaultValue(false); b.Property("ExtraProperties") - .HasColumnName("ExtraProperties"); + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); - b.Property("IsDeleted"); + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnName("IsDeleted") + .HasColumnType("bit") + .HasDefaultValue(false); - b.Property("LastModificationTime"); + b.Property("LastModificationTime") + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); - b.Property("LastModifierId"); + b.Property("LastModifierId") + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); b.Property("LockoutEnabled") .ValueGeneratedOnAdd() .HasColumnName("LockoutEnabled") + .HasColumnType("bit") .HasDefaultValue(false); - b.Property("LockoutEnd"); + b.Property("LockoutEnd") + .HasColumnType("datetimeoffset"); b.Property("Name") .HasColumnName("Name") + .HasColumnType("nvarchar(64)") .HasMaxLength(64); b.Property("NormalizedEmail") + .IsRequired() .HasColumnName("NormalizedEmail") + .HasColumnType("nvarchar(256)") .HasMaxLength(256); b.Property("NormalizedUserName") .IsRequired() .HasColumnName("NormalizedUserName") + .HasColumnType("nvarchar(256)") .HasMaxLength(256); b.Property("PasswordHash") .HasColumnName("PasswordHash") + .HasColumnType("nvarchar(256)") .HasMaxLength(256); b.Property("PhoneNumber") .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") .HasMaxLength(16); b.Property("PhoneNumberConfirmed") .ValueGeneratedOnAdd() .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") .HasDefaultValue(false); b.Property("SecurityStamp") .IsRequired() .HasColumnName("SecurityStamp") + .HasColumnType("nvarchar(256)") .HasMaxLength(256); b.Property("Surname") .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") .HasMaxLength(64); b.Property("TenantId") - .HasColumnName("TenantId"); + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); b.Property("TwoFactorEnabled") .ValueGeneratedOnAdd() .HasColumnName("TwoFactorEnabled") + .HasColumnType("bit") .HasDefaultValue(false); b.Property("UserName") .IsRequired() .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") .HasMaxLength(256); b.HasKey("Id"); @@ -219,18 +293,23 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .HasColumnType("uniqueidentifier"); b.Property("ClaimType") .IsRequired() + .HasColumnType("nvarchar(256)") .HasMaxLength(256); b.Property("ClaimValue") + .HasColumnType("nvarchar(1024)") .HasMaxLength(1024); - b.Property("TenantId"); + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); - b.Property("UserId"); + b.Property("UserId") + .HasColumnType("uniqueidentifier"); b.HasKey("Id"); @@ -241,19 +320,25 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { - b.Property("UserId"); + b.Property("UserId") + .HasColumnType("uniqueidentifier"); b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") .HasMaxLength(64); b.Property("ProviderDisplayName") + .HasColumnType("nvarchar(128)") .HasMaxLength(128); b.Property("ProviderKey") .IsRequired() + .HasColumnType("nvarchar(196)") .HasMaxLength(196); - b.Property("TenantId"); + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); b.HasKey("UserId", "LoginProvider"); @@ -264,11 +349,15 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => { - b.Property("UserId"); + b.Property("UserId") + .HasColumnType("uniqueidentifier"); - b.Property("RoleId"); + b.Property("RoleId") + .HasColumnType("uniqueidentifier"); - b.Property("TenantId"); + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); b.HasKey("UserId", "RoleId"); @@ -279,17 +368,23 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => { - b.Property("UserId"); + b.Property("UserId") + .HasColumnType("uniqueidentifier"); b.Property("LoginProvider") + .HasColumnType("nvarchar(64)") .HasMaxLength(64); b.Property("Name") + .HasColumnType("nvarchar(128)") .HasMaxLength(128); - b.Property("TenantId"); + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); - b.Property("Value"); + b.Property("Value") + .HasColumnType("nvarchar(max)"); b.HasKey("UserId", "LoginProvider", "Name"); @@ -299,21 +394,27 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); b.Property("Name") .IsRequired() + .HasColumnType("nvarchar(128)") .HasMaxLength(128); b.Property("ProviderKey") .IsRequired() + .HasColumnType("nvarchar(64)") .HasMaxLength(64); b.Property("ProviderName") .IsRequired() + .HasColumnType("nvarchar(64)") .HasMaxLength(64); - b.Property("TenantId"); + b.Property("TenantId") + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); b.HasKey("Id"); @@ -325,20 +426,25 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); b.Property("Name") .IsRequired() + .HasColumnType("nvarchar(128)") .HasMaxLength(128); b.Property("ProviderKey") + .HasColumnType("nvarchar(64)") .HasMaxLength(64); b.Property("ProviderName") + .HasColumnType("nvarchar(64)") .HasMaxLength(64); b.Property("Value") .IsRequired() + .HasColumnType("nvarchar(2048)") .HasMaxLength(2048); b.HasKey("Id"); @@ -351,48 +457,63 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Blogging.Blogs.Blog", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); - b.Property("ConcurrencyStamp"); + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); b.Property("CreationTime") - .HasColumnName("CreationTime"); + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); b.Property("CreatorId") - .HasColumnName("CreatorId"); + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); b.Property("DeleterId") - .HasColumnName("DeleterId"); + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); b.Property("DeletionTime") - .HasColumnName("DeletionTime"); + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); b.Property("Description") .HasColumnName("Description") + .HasColumnType("nvarchar(1024)") .HasMaxLength(1024); b.Property("ExtraProperties") - .HasColumnName("ExtraProperties"); + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnName("IsDeleted") + .HasColumnType("bit") .HasDefaultValue(false); b.Property("LastModificationTime") - .HasColumnName("LastModificationTime"); + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); b.Property("LastModifierId") - .HasColumnName("LastModifierId"); + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); b.Property("Name") .IsRequired() .HasColumnName("Name") + .HasColumnType("nvarchar(256)") .HasMaxLength(256); b.Property("ShortName") .IsRequired() .HasColumnName("ShortName") + .HasColumnType("nvarchar(32)") .HasMaxLength(32); b.HasKey("Id"); @@ -403,45 +524,60 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Blogging.Comments.Comment", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); - b.Property("ConcurrencyStamp"); + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); b.Property("CreationTime") - .HasColumnName("CreationTime"); + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); b.Property("CreatorId") - .HasColumnName("CreatorId"); + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); b.Property("DeleterId") - .HasColumnName("DeleterId"); + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); b.Property("DeletionTime") - .HasColumnName("DeletionTime"); + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); b.Property("ExtraProperties") - .HasColumnName("ExtraProperties"); + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnName("IsDeleted") + .HasColumnType("bit") .HasDefaultValue(false); b.Property("LastModificationTime") - .HasColumnName("LastModificationTime"); + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); b.Property("LastModifierId") - .HasColumnName("LastModifierId"); + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); b.Property("PostId") - .HasColumnName("PostId"); + .HasColumnName("PostId") + .HasColumnType("uniqueidentifier"); b.Property("RepliedCommentId") - .HasColumnName("RepliedCommentId"); + .HasColumnName("RepliedCommentId") + .HasColumnType("uniqueidentifier"); b.Property("Text") .IsRequired() .HasColumnName("Text") + .HasColumnType("nvarchar(1024)") .HasMaxLength(1024); b.HasKey("Id"); @@ -456,57 +592,80 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Blogging.Posts.Post", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); b.Property("BlogId") - .HasColumnName("BlogId"); + .HasColumnName("BlogId") + .HasColumnType("uniqueidentifier"); - b.Property("ConcurrencyStamp"); + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); b.Property("Content") .HasColumnName("Content") + .HasColumnType("nvarchar(max)") .HasMaxLength(1048576); b.Property("CoverImage") .IsRequired() - .HasColumnName("CoverImage"); + .HasColumnName("CoverImage") + .HasColumnType("nvarchar(max)"); b.Property("CreationTime") - .HasColumnName("CreationTime"); + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); b.Property("CreatorId") - .HasColumnName("CreatorId"); + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); b.Property("DeleterId") - .HasColumnName("DeleterId"); + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); b.Property("DeletionTime") - .HasColumnName("DeletionTime"); + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnName("Description") + .HasColumnType("nvarchar(1000)") + .HasMaxLength(1000); b.Property("ExtraProperties") - .HasColumnName("ExtraProperties"); + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnName("IsDeleted") + .HasColumnType("bit") .HasDefaultValue(false); b.Property("LastModificationTime") - .HasColumnName("LastModificationTime"); + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); b.Property("LastModifierId") - .HasColumnName("LastModifierId"); + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); - b.Property("ReadCount"); + b.Property("ReadCount") + .HasColumnType("int"); b.Property("Title") .IsRequired() .HasColumnName("Title") + .HasColumnType("nvarchar(512)") .HasMaxLength(512); b.Property("Url") .IsRequired() .HasColumnName("Url") + .HasColumnType("nvarchar(64)") .HasMaxLength(64); b.HasKey("Id"); @@ -519,14 +678,20 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b => { b.Property("PostId") - .HasColumnName("PostId"); + .HasColumnName("PostId") + .HasColumnType("uniqueidentifier"); b.Property("TagId") - .HasColumnName("TagId"); + .HasColumnName("TagId") + .HasColumnType("uniqueidentifier"); - b.Property("CreationTime"); + b.Property("CreationTime") + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); - b.Property("CreatorId"); + b.Property("CreatorId") + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); b.HasKey("PostId", "TagId"); @@ -538,49 +703,65 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Blogging.Tagging.Tag", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); - b.Property("BlogId"); + b.Property("BlogId") + .HasColumnType("uniqueidentifier"); - b.Property("ConcurrencyStamp"); + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); b.Property("CreationTime") - .HasColumnName("CreationTime"); + .HasColumnName("CreationTime") + .HasColumnType("datetime2"); b.Property("CreatorId") - .HasColumnName("CreatorId"); + .HasColumnName("CreatorId") + .HasColumnType("uniqueidentifier"); b.Property("DeleterId") - .HasColumnName("DeleterId"); + .HasColumnName("DeleterId") + .HasColumnType("uniqueidentifier"); b.Property("DeletionTime") - .HasColumnName("DeletionTime"); + .HasColumnName("DeletionTime") + .HasColumnType("datetime2"); b.Property("Description") .HasColumnName("Description") + .HasColumnType("nvarchar(512)") .HasMaxLength(512); b.Property("ExtraProperties") - .HasColumnName("ExtraProperties"); + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); b.Property("IsDeleted") .ValueGeneratedOnAdd() .HasColumnName("IsDeleted") + .HasColumnType("bit") .HasDefaultValue(false); b.Property("LastModificationTime") - .HasColumnName("LastModificationTime"); + .HasColumnName("LastModificationTime") + .HasColumnType("datetime2"); b.Property("LastModifierId") - .HasColumnName("LastModifierId"); + .HasColumnName("LastModifierId") + .HasColumnType("uniqueidentifier"); b.Property("Name") .IsRequired() .HasColumnName("Name") + .HasColumnType("nvarchar(64)") .HasMaxLength(64); b.Property("UsageCount") - .HasColumnName("UsageCount"); + .HasColumnName("UsageCount") + .HasColumnType("int"); b.HasKey("Id"); @@ -590,45 +771,59 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Blogging.Users.BlogUser", b => { b.Property("Id") - .ValueGeneratedOnAdd(); + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); - b.Property("ConcurrencyStamp"); + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnName("ConcurrencyStamp") + .HasColumnType("nvarchar(max)"); b.Property("Email") + .IsRequired() .HasColumnName("Email") + .HasColumnType("nvarchar(256)") .HasMaxLength(256); b.Property("EmailConfirmed") .ValueGeneratedOnAdd() .HasColumnName("EmailConfirmed") + .HasColumnType("bit") .HasDefaultValue(false); b.Property("ExtraProperties") - .HasColumnName("ExtraProperties"); + .HasColumnName("ExtraProperties") + .HasColumnType("nvarchar(max)"); b.Property("Name") .HasColumnName("Name") + .HasColumnType("nvarchar(64)") .HasMaxLength(64); b.Property("PhoneNumber") .HasColumnName("PhoneNumber") + .HasColumnType("nvarchar(16)") .HasMaxLength(16); b.Property("PhoneNumberConfirmed") .ValueGeneratedOnAdd() .HasColumnName("PhoneNumberConfirmed") + .HasColumnType("bit") .HasDefaultValue(false); b.Property("Surname") .HasColumnName("Surname") + .HasColumnType("nvarchar(64)") .HasMaxLength(64); b.Property("TenantId") - .HasColumnName("TenantId"); + .HasColumnName("TenantId") + .HasColumnType("uniqueidentifier"); b.Property("UserName") .IsRequired() .HasColumnName("UserName") + .HasColumnType("nvarchar(256)") .HasMaxLength(256); b.HasKey("Id"); @@ -638,80 +833,90 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => { - b.HasOne("Volo.Abp.Identity.IdentityRole") + b.HasOne("Volo.Abp.Identity.IdentityRole", null) .WithMany("Claims") .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b => { - b.HasOne("Volo.Abp.Identity.IdentityUser") + b.HasOne("Volo.Abp.Identity.IdentityUser", null) .WithMany("Claims") .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b => { - b.HasOne("Volo.Abp.Identity.IdentityUser") + b.HasOne("Volo.Abp.Identity.IdentityUser", null) .WithMany("Logins") .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b => { - b.HasOne("Volo.Abp.Identity.IdentityRole") + b.HasOne("Volo.Abp.Identity.IdentityRole", null) .WithMany() .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.HasOne("Volo.Abp.Identity.IdentityUser") + b.HasOne("Volo.Abp.Identity.IdentityUser", null) .WithMany("Roles") .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b => { - b.HasOne("Volo.Abp.Identity.IdentityUser") + b.HasOne("Volo.Abp.Identity.IdentityUser", null) .WithMany("Tokens") .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Volo.Blogging.Comments.Comment", b => { - b.HasOne("Volo.Blogging.Posts.Post") + b.HasOne("Volo.Blogging.Posts.Post", null) .WithMany() .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.HasOne("Volo.Blogging.Comments.Comment") + b.HasOne("Volo.Blogging.Comments.Comment", null) .WithMany() .HasForeignKey("RepliedCommentId"); }); modelBuilder.Entity("Volo.Blogging.Posts.Post", b => { - b.HasOne("Volo.Blogging.Blogs.Blog") + b.HasOne("Volo.Blogging.Blogs.Blog", null) .WithMany() .HasForeignKey("BlogId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b => { - b.HasOne("Volo.Blogging.Posts.Post") + b.HasOne("Volo.Blogging.Posts.Post", null) .WithMany("Tags") .HasForeignKey("PostId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); - b.HasOne("Volo.Blogging.Tagging.Tag") + b.HasOne("Volo.Blogging.Tagging.Tag", null) .WithMany() .HasForeignKey("TagId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); }); #pragma warning restore 612, 618 } diff --git a/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj b/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj index 16fc44f6e3..23e2ecb5d3 100644 --- a/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj +++ b/modules/blogging/app/Volo.BloggingTestApp/Volo.BloggingTestApp.csproj @@ -4,6 +4,7 @@ netcoreapp3.1 + InProcess @@ -17,8 +18,8 @@ - - + + diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/RawFileDto.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/RawFileDto.cs index 56a9653b73..d9b0a4c636 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/RawFileDto.cs +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Files/RawFileDto.cs @@ -1,7 +1,22 @@ -namespace Volo.Blogging.Files +using System.Security.Cryptography; +using Volo.Abp.Threading; + +namespace Volo.Blogging.Files { public class RawFileDto { public byte[] Bytes { get; set; } + + public bool IsFileEmpty => Bytes == null || Bytes.Length == 0; + + public RawFileDto() + { + + } + + public static RawFileDto EmptyResult() + { + return new RawFileDto() {Bytes = new byte[0]}; + } } } \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/CreatePostDto.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/CreatePostDto.cs index 530bb6efc1..164e4f691f 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/CreatePostDto.cs +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/CreatePostDto.cs @@ -22,5 +22,9 @@ namespace Volo.Blogging.Posts public string Content { get; set; } public string Tags { get; set; } + + [StringLength(PostConsts.MaxDescriptionLength)] + public string Description { get; set; } + } } diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/PostWithDetailsDto.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/PostWithDetailsDto.cs index 08dde7e019..15401afbc6 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/PostWithDetailsDto.cs +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/PostWithDetailsDto.cs @@ -18,6 +18,8 @@ namespace Volo.Blogging.Posts public string Content { get; set; } + public string Description { get; set; } + public int ReadCount { get; set; } public int CommentCount { get; set; } diff --git a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/UpdatePostDto.cs b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/UpdatePostDto.cs index 07c62ba786..16b54fedd6 100644 --- a/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/UpdatePostDto.cs +++ b/modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/UpdatePostDto.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; namespace Volo.Blogging.Posts { @@ -14,6 +15,8 @@ namespace Volo.Blogging.Posts public string Content { get; set; } + public string Description { get; set; } + public string Tags { get; set; } } } diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs index 4673f5c6d1..cdfd4cb836 100644 --- a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Files/FileAppService.cs @@ -2,6 +2,7 @@ using System.ComponentModel.DataAnnotations; using System.IO; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Volo.Abp; using Volo.Abp.Validation; @@ -12,9 +13,11 @@ namespace Volo.Blogging.Files public class FileAppService : BloggingAppServiceBase, IFileAppService { public BlogFileOptions Options { get; } + private readonly ILogger _logger; - public FileAppService(IOptions options) + public FileAppService(IOptions options, ILogger logger) { + _logger = logger; Options = options.Value; } @@ -35,12 +38,13 @@ namespace Volo.Blogging.Files var filePath = Path.Combine(Options.FileUploadLocalFolder, name); - return Task.FromResult( - new RawFileDto - { - Bytes = File.ReadAllBytes(filePath) - } - ); + if (File.Exists(filePath)) + { + return Task.FromResult(new RawFileDto {Bytes = File.ReadAllBytes(filePath)}); + } + + _logger.LogError($"Cannot find the file {filePath}"); + return Task.FromResult(RawFileDto.EmptyResult()); } public virtual Task CreateAsync(FileUploadInputDto input) diff --git a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs index 44b8e9866e..798bf8e39f 100644 --- a/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs +++ b/modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs @@ -149,6 +149,7 @@ namespace Volo.Blogging.Posts post.SetTitle(input.Title); post.SetUrl(input.Url); post.Content = input.Content; + post.Description = input.Description; post.CoverImage = input.CoverImage; post = await _postRepository.UpdateAsync(post); @@ -171,7 +172,10 @@ namespace Volo.Blogging.Posts coverImage: input.CoverImage, url: input.Url ) - { Content = input.Content }; + { + Content = input.Content, + Description = input.Description + }; await _postRepository.InsertAsync(post); @@ -202,7 +206,7 @@ namespace Volo.Blogging.Posts private async Task RemoveOldTags(ICollection newTags, Post post) { - foreach (var oldTag in post.Tags) + foreach (var oldTag in post.Tags.ToList()) { var tag = await _tagRepository.GetAsync(oldTag.TagId); diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json index 24b4f83b21..18f8e00c8a 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Localization/Resources/en.json @@ -43,6 +43,7 @@ "Description": "Description", "Blogs": "Blogs", "Tags": "Tags", - "ShareOn": "Share on" + "ShareOn": "Share on", + "TitleLengthWarning": "Keep your title size under 60 characters to be SEO friendly!" } } \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Posts/PostConsts.cs b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Posts/PostConsts.cs index 7cfb5b1b30..665984dfbb 100644 --- a/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Posts/PostConsts.cs +++ b/modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Posts/PostConsts.cs @@ -7,5 +7,11 @@ public const int MaxUrlLength = 64; public const int MaxContentLength = 1024 * 1024; //1MB + + public const int MaxDescriptionLength = 1000; + + public const int MaxTitleLengthToBeSeoFriendly = 60; + + public const int MaxSeoFriendlyDescriptionLength = 200; } } diff --git a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/Post.cs b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/Post.cs index ee44e7e029..ecc21d59d6 100644 --- a/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/Post.cs +++ b/modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/Post.cs @@ -23,13 +23,16 @@ namespace Volo.Blogging.Posts [CanBeNull] public virtual string Content { get; set; } + [CanBeNull] + public virtual string Description { get; set; } + public virtual int ReadCount { get; protected set; } public virtual Collection Tags { get; protected set; } protected Post() { - + } public Post(Guid id, Guid blogId, [NotNull] string title, [NotNull] string coverImage, [NotNull] string url) @@ -63,7 +66,7 @@ namespace Volo.Blogging.Posts public virtual void AddTag(Guid tagId) { - Tags.Add(new PostTag(Id,tagId)); + Tags.Add(new PostTag(Id, tagId)); } public virtual void RemoveTag(Guid tagId) diff --git a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContextModelBuilderExtensions.cs b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContextModelBuilderExtensions.cs index 00fd20dbdc..a5e909d8a4 100644 --- a/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContextModelBuilderExtensions.cs +++ b/modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContextModelBuilderExtensions.cs @@ -31,15 +31,15 @@ namespace Volo.Blogging.EntityFrameworkCore { b.ToTable(options.TablePrefix + "Users", options.Schema); + b.ConfigureByConvention(); b.ConfigureAbpUser(); - b.ConfigureExtraProperties(); }); builder.Entity(b => { b.ToTable(options.TablePrefix + "Blogs", options.Schema); - b.ConfigureFullAuditedAggregateRoot(); + b.ConfigureByConvention(); b.Property(x => x.Name).IsRequired().HasMaxLength(BlogConsts.MaxNameLength).HasColumnName(nameof(Blog.Name)); b.Property(x => x.ShortName).IsRequired().HasMaxLength(BlogConsts.MaxShortNameLength).HasColumnName(nameof(Blog.ShortName)); @@ -50,13 +50,14 @@ namespace Volo.Blogging.EntityFrameworkCore { b.ToTable(options.TablePrefix + "Posts", options.Schema); - b.ConfigureFullAuditedAggregateRoot(); + b.ConfigureByConvention(); b.Property(x => x.BlogId).HasColumnName(nameof(Post.BlogId)); b.Property(x => x.Title).IsRequired().HasMaxLength(PostConsts.MaxTitleLength).HasColumnName(nameof(Post.Title)); b.Property(x => x.CoverImage).IsRequired().HasColumnName(nameof(Post.CoverImage)); b.Property(x => x.Url).IsRequired().HasMaxLength(PostConsts.MaxUrlLength).HasColumnName(nameof(Post.Url)); b.Property(x => x.Content).IsRequired(false).HasMaxLength(PostConsts.MaxContentLength).HasColumnName(nameof(Post.Content)); + b.Property(x => x.Description).IsRequired(false).HasMaxLength(PostConsts.MaxDescriptionLength).HasColumnName(nameof(Post.Description)); b.HasMany(p => p.Tags).WithOne().HasForeignKey(qt => qt.PostId); @@ -67,7 +68,7 @@ namespace Volo.Blogging.EntityFrameworkCore { b.ToTable(options.TablePrefix + "Comments", options.Schema); - b.ConfigureFullAuditedAggregateRoot(); + b.ConfigureByConvention(); b.Property(x => x.Text).IsRequired().HasMaxLength(CommentConsts.MaxTextLength).HasColumnName(nameof(Comment.Text)); b.Property(x => x.RepliedCommentId).HasColumnName(nameof(Comment.RepliedCommentId)); @@ -81,7 +82,7 @@ namespace Volo.Blogging.EntityFrameworkCore { b.ToTable(options.TablePrefix + "Tags", options.Schema); - b.ConfigureFullAuditedAggregateRoot(); + b.ConfigureByConvention(); b.Property(x => x.Name).IsRequired().HasMaxLength(TagConsts.MaxNameLength).HasColumnName(nameof(Tag.Name)); b.Property(x => x.Description).HasMaxLength(TagConsts.MaxDescriptionLength).HasColumnName(nameof(Tag.Description)); @@ -94,6 +95,8 @@ namespace Volo.Blogging.EntityFrameworkCore { b.ToTable(options.TablePrefix + "PostTags", options.Schema); + b.ConfigureByConvention(); + b.Property(x => x.PostId).HasColumnName(nameof(PostTag.PostId)); b.Property(x => x.TagId).HasColumnName(nameof(PostTag.TagId)); diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml index 5c781d1a5a..7bed3d0d68 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Detail.cshtml @@ -9,7 +9,8 @@ @inject IAuthorizationService Authorization @model DetailModel @{ - ViewBag.PageTitle = Model.Post.Title; + ViewBag.Title = Model.Post.Title; + ViewBag.Description = Model.Post.Description; var hasCommentingPermission = CurrentUser.IsAuthenticated; //TODO: Apply real policy! } @section scripts { diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml index 4baf807e47..9817c4f092 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml @@ -1,6 +1,7 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Packages.TuiEditor @using Volo.Blogging.Pages.Blog.Posts +@using Volo.Blogging.Posts @model EditModel @inherits Volo.Blogging.Pages.Blog.BloggingPage @{ @@ -26,9 +27,18 @@
+ + + - +
@@ -41,7 +51,9 @@ + + diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml.cs b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml.cs index 9c87723edd..2203aacbea 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml.cs +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/Edit.cshtml.cs @@ -42,7 +42,7 @@ namespace Volo.Blogging.Pages.Blog.Posts var postDto = await _postAppService.GetAsync(new Guid(PostId)); Post = ObjectMapper.Map(postDto); - Post.Tags = String.Join(", ", postDto.Tags.Select(p=>p.Name).ToArray()); + Post.Tags = String.Join(", ", postDto.Tags.Select(p => p.Name).ToArray()); return Page(); } @@ -56,7 +56,10 @@ namespace Volo.Blogging.Pages.Blog.Posts Url = Post.Url, CoverImage = Post.CoverImage, Content = Post.Content, - Tags = Post.Tags + Tags = Post.Tags, + Description = Post.Description.IsNullOrEmpty() ? + Post.Content.Truncate(PostConsts.MaxSeoFriendlyDescriptionLength) : + Post.Description }; var editedPost = await _postAppService.UpdateAsync(Post.Id, post); @@ -92,6 +95,9 @@ namespace Volo.Blogging.Pages.Blog.Posts [StringLength(PostConsts.MaxContentLength)] public string Content { get; set; } + [StringLength(PostConsts.MaxDescriptionLength)] + public string Description { get; set; } + public string Tags { get; set; } } } \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml index d26c178898..9139fe5ce1 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml @@ -1,6 +1,7 @@ @page @using Volo.Abp.AspNetCore.Mvc.UI.Packages.TuiEditor @using Volo.Blogging.Pages.Blog.Posts +@using Volo.Blogging.Posts @model NewModel @inherits Volo.Blogging.Pages.Blog.BloggingPage @{ @@ -27,6 +28,14 @@ + + + @@ -44,6 +53,7 @@ +
@@ -69,10 +79,10 @@ @L["Cancel"]
-
+
- + diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml.cs b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml.cs index b566b7bbe0..353e469590 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml.cs +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/New.cshtml.cs @@ -54,7 +54,13 @@ namespace Volo.Blogging.Pages.Blog.Posts public async Task OnPost() { var blog = await _blogAppService.GetAsync(Post.BlogId); - var postWithDetailsDto = await _postAppService.CreateAsync(ObjectMapper.Map(Post)); + + if (string.IsNullOrEmpty(Post.Description)) + { + Post.Description = Post.Content.Truncate(PostConsts.MaxSeoFriendlyDescriptionLength); + } + + var postWithDetailsDto = await _postAppService.CreateAsync(ObjectMapper.Map(Post)); //TODO: Try Url.Page(...) var urlPrefix = _blogOptions.RoutePrefix; @@ -84,6 +90,10 @@ namespace Volo.Blogging.Pages.Blog.Posts public string Content { get; set; } public string Tags { get; set; } + + [StringLength(PostConsts.MaxDescriptionLength)] + public string Description { get; set; } + } } } \ No newline at end of file diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/edit.js b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/edit.js index 6b0dd9f02b..e02ec975fe 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/edit.js +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/edit.js @@ -5,10 +5,18 @@ var $submitButton = $container.find("button[type=submit]"); var $form = $container.find("form#edit-post-form"); var editorDataKey = "tuiEditor"; + var $titleLengthWarning = $("#title-length-warning"); + var maxTitleLength = parseInt($titleLengthWarning.data("max-length")); + + var $title = $('#Post_Title'); + var $coverImage = $("#CoverImage"); + var $postCoverImage = $('#Post_CoverImage'); + var $coverImageFile = $('#CoverImageFile'); + var setCoverImage = function (file) { - $('#Post_CoverImage').val(file.fileUrl); - $("#CoverImage").attr("src", file.fileUrl); + $postCoverImage.val(file.fileUrl); + $coverImage.attr("src", file.fileUrl); }; var uploadCoverImage = function (file) { @@ -27,11 +35,28 @@ }); }; - $('#CoverImageFile').change(function () { - if (!$('#CoverImageFile').prop('files').length) { + var checkTitleLength = function () { + var title = $title.val(); + + if (title.length > maxTitleLength) { + $titleLengthWarning.show(); + } else { + $titleLengthWarning.hide(); + } + }; + + checkTitleLength(); + + $title.on("change paste keyup", function () { + checkTitleLength(); + }); + + $coverImageFile.change(function () { + if (!$coverImageFile.prop('files').length) { return; } - var file = $('#CoverImageFile').prop('files')[0]; + + var file = $coverImageFile.prop('files')[0]; uploadCoverImage(file); }); @@ -93,6 +118,7 @@ $submitButton.buttonBusy(); $(this).off('submit').submit(); + return true; }); }); diff --git a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js index 0c3e927264..3edacd49b5 100644 --- a/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js +++ b/modules/blogging/src/Volo.Blogging.Web/Pages/Blogs/Posts/new.js @@ -5,11 +5,18 @@ var $submitButton = $container.find("button[type=submit]"); var $form = $container.find("form#new-post-form"); var editorDataKey = "tuiEditor"; + var $titleLengthWarning = $("#title-length-warning"); + var maxTitleLength = parseInt($titleLengthWarning.data("max-length")); + var $title = $('#Post_Title'); + var $url = $('#Post_Url'); + var $coverImage = $("#CoverImage"); + var $postCoverImage = $('#Post_CoverImage'); + var $coverImageFile = $('#CoverImageFile'); var setCoverImage = function (file) { - $('#Post_CoverImage').val(file.fileUrl); - $("#CoverImage").attr("src", file.fileUrl); - $("#CoverImage").show(); + $postCoverImage.val(file.fileUrl); + $coverImage.attr("src", file.fileUrl); + $coverImage.show(); }; var uploadCoverImage = function (file) { @@ -28,11 +35,11 @@ }); }; - $('#CoverImageFile').change(function () { - if (!$('#CoverImageFile').prop('files').length) { + $coverImageFile.change(function () { + if (!$coverImageFile.prop('files').length) { return; } - var file = $('#CoverImageFile').prop('files')[0]; + var file = $coverImageFile.prop('files')[0]; uploadCoverImage(file); }); @@ -40,7 +47,7 @@ var formData = new FormData(); formData.append('file', file); - $.ajax({ + $.ajax({ type: "POST", url: "/api/blogging/files/images/upload", data: formData, @@ -92,33 +99,36 @@ $submitButton.buttonBusy(); $(this).off('submit').submit(); + return true; }); var urlEdited = false; var reflectedChange = false; - $('#Post_Title').on("change paste keyup", function () { + $title.on("change paste keyup", function () { if (urlEdited) { return; } - var title = $('#Post_Title').val(); + var title = $title.val(); - if (title.length > 64) { - title = title.substring(0, 64); + if (title.length > maxTitleLength) { + $titleLengthWarning.show(); + } else { + $titleLengthWarning.hide(); } title = title.replace(' ', '-'); title = title.replace(new RegExp(' ', 'g'), '-'); reflectedChange = true; - $('#Post_Url').val(title); + $url.val(title); reflectedChange = false; }); - $('#Post_Url').change(function () { + $url.change(function () { if (!reflectedChange) { urlEdited = true; } }); -}); +}); \ No newline at end of file diff --git a/modules/docs/app/VoloDocs.Web/Controllers/HomeController.cs b/modules/docs/app/VoloDocs.Web/Controllers/HomeController.cs deleted file mode 100644 index 5594306229..0000000000 --- a/modules/docs/app/VoloDocs.Web/Controllers/HomeController.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Volo.Abp.AspNetCore.Mvc; - -namespace VoloDocs.Web.Controllers -{ - public class HomeController : AbpController - { - public void Index() - { - - } - } -} diff --git a/modules/docs/app/VoloDocs.Web/Pages/Index.cshtml b/modules/docs/app/VoloDocs.Web/Pages/Index.cshtml index 0d75012326..0a212f1499 100644 --- a/modules/docs/app/VoloDocs.Web/Pages/Index.cshtml +++ b/modules/docs/app/VoloDocs.Web/Pages/Index.cshtml @@ -3,3 +3,21 @@ @{ } +@if (!Model.Projects.Any()) +{ + + No projects found!
+ See documentation to see how you can create a new one. +
+} +else +{ +

Projects

+ + + @foreach (var project in Model.Projects) + { + @project.Name + } + +} \ No newline at end of file diff --git a/modules/docs/app/VoloDocs.Web/Pages/Index.cshtml.cs b/modules/docs/app/VoloDocs.Web/Pages/Index.cshtml.cs index b127db4b3f..537d650f83 100644 --- a/modules/docs/app/VoloDocs.Web/Pages/Index.cshtml.cs +++ b/modules/docs/app/VoloDocs.Web/Pages/Index.cshtml.cs @@ -1,29 +1,59 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Options; using Volo.Docs; +using Volo.Docs.Projects; namespace VoloDocs.Web.Pages { public class IndexModel : PageModel { + public IReadOnlyList Projects { get; set; } + private readonly DocsUiOptions _urlUiOptions; - public IndexModel(IOptions urlOptions) + private readonly IProjectAppService _projectAppService; + + public IndexModel(IOptions urlOptions, IProjectAppService projectAppService) { + _projectAppService = projectAppService; _urlUiOptions = urlOptions.Value; } - public IActionResult OnGet() + public async Task OnGetAsync() { - //TODO: Create HomeController & Index instead of Page. Otherwise, we have an empty Index.cshtml file. - if (!_urlUiOptions.RoutePrefix.IsNullOrWhiteSpace()) + var projects = await _projectAppService.GetListAsync(); + + if (projects.Items.Count == 1) { - return Redirect("." + _urlUiOptions.RoutePrefix); + return await RedirectToProjectAsync(projects.Items.First()); + } + else if (projects.Items.Count > 1) + { + Projects = projects.Items; } return Page(); } + + private async Task RedirectToProjectAsync(ProjectDto project, string language = "en", string version = null) + { + var path = GetUrlForProject(project, language, version); + return await Task.FromResult(Redirect(path)); + } + + //Eg: "/en/abp/latest" + public string GetUrlForProject(ProjectDto project, string language = "en", string version = null) + { + return "." + + _urlUiOptions.RoutePrefix.EnsureStartsWith('/').EnsureEndsWith('/') + + language.EnsureEndsWith('/') + + project.ShortName.EnsureEndsWith('/') + + (version ?? DocsAppConsts.Latest); + } } -} \ No newline at end of file +} diff --git a/modules/docs/app/VoloDocs.Web/appsettings.json b/modules/docs/app/VoloDocs.Web/appsettings.json index db1d4b31f6..8eea6b57cd 100644 --- a/modules/docs/app/VoloDocs.Web/appsettings.json +++ b/modules/docs/app/VoloDocs.Web/appsettings.json @@ -3,5 +3,10 @@ "LogoUrl": "/assets/images/Logo.png", "ElasticSearch": { "Url": "http://localhost:9200" + }, + "Volo.Docs": { + "DocumentCacheTimeoutInterval": "24:00:00", + "DocumentResource.AbsoluteExpirationRelativeToNow": "24:00:00", + "DocumentResource.SlidingExpiration": "02:00:00" } } \ No newline at end of file diff --git a/modules/docs/app/VoloDocs.Web/package-lock.json b/modules/docs/app/VoloDocs.Web/package-lock.json index a666d3cc7c..e764349469 100644 --- a/modules/docs/app/VoloDocs.Web/package-lock.json +++ b/modules/docs/app/VoloDocs.Web/package-lock.json @@ -5,247 +5,256 @@ "requires": true, "dependencies": { "@abp/anchor-js": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@abp/anchor-js/-/anchor-js-0.5.1.tgz", - "integrity": "sha512-9N/iPP9tDdq9lKRNFQuqRL+QYSv5fq789KgHHGG1/MqExJ6KTPcqUDHvQ53kz96pUgJA+Fyn3dpwgPqLVYI3Yg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/anchor-js/-/anchor-js-1.1.1.tgz", + "integrity": "sha512-hHyYYJ09hhT5xeQJUsBN43yT+y49FKcigq4Wrx8448TrW7r2NJD5i3Xy3BGEstNSlfcQPTsmciQnbkcU2rX1HQ==", "requires": { - "@abp/core": "^0.4.9", - "anchor-js": "^4.1.1" + "@abp/core": "^1.1.1", + "anchor-js": "^4.2.2" } }, "@abp/aspnetcore.mvc.ui": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-0.4.9.tgz", - "integrity": "sha512-AveMEi6WRQmD1tM9yVNLAe6ffcbtoL3ZGQKTPA95Q+dy5xBXVg4Y8jdhxawNPK64KYArhfrVkQDfa4mq3pW5Qw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-1.1.1.tgz", + "integrity": "sha512-wZbptVCSxZzEjkJx+/sWrH9Pikp9nOy7V8Htz+L+S7/qAzfXu5PRVV8ahddfAcDHRk30buRhdbJlCVdt6hkZ6g==", "requires": { - "ansi-colors": "^1.1.0", + "ansi-colors": "^4.1.1", "extend-object": "^1.0.0", - "gulp": "^3.9.1", - "merge-stream": "^1.0.1", + "gulp": "^4.0.2", + "merge-stream": "^2.0.0", "path": "^0.12.7", - "rimraf": "^2.6.2" + "rimraf": "^3.0.0" } }, "@abp/aspnetcore.mvc.ui.theme.basic": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-0.4.9.tgz", - "integrity": "sha512-g3Zby8x8+vWw3oTJsSPasjm4tv7BG4270PEf7jK9w925+lIyKYHn6UxOcFbSU2pi9mJTnaF+Fk/rScIcdxwZZw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-1.1.1.tgz", + "integrity": "sha512-ooXtCM3TWN69RU7xs6avnzOQBXzsiHY5BEIogzSBialZC4uG5H56qrIr4MbsFNae+PQM23Mw2tnJ/Z7dutURCQ==", "requires": { - "@abp/aspnetcore.mvc.ui.theme.shared": "^0.4.9" + "@abp/aspnetcore.mvc.ui.theme.shared": "^1.1.1" } }, "@abp/aspnetcore.mvc.ui.theme.shared": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-0.4.9.tgz", - "integrity": "sha512-Fe4UfQ215Pz2V6D5HUYhDJFeJWjKLcv2gsZfOheWLlOBgYh274pdnUIwceoZ4Btpa9aPECLG+LAH4dTbs8vjRg==", - "requires": { - "@abp/aspnetcore.mvc.ui": "^0.4.9", - "@abp/bootstrap": "^0.4.9", - "@abp/datatables.net-bs4": "^0.4.9", - "@abp/font-awesome": "^0.4.9", - "@abp/jquery-form": "^0.4.9", - "@abp/jquery-validation-unobtrusive": "^0.4.9", - "@abp/lodash": "^0.4.9", - "@abp/select2": "^0.4.9", - "@abp/sweetalert": "^0.4.9", - "@abp/timeago": "^0.4.9", - "@abp/toastr": "^0.4.9" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-1.1.1.tgz", + "integrity": "sha512-LilSyefzT1+rcTU7vbWxcO8TwBgGZIx6QbMUrDicSTH6LLJ9S5+yNaGNJbbZKDG6qx0BEoC1u8dE8KCUshwxoQ==", + "requires": { + "@abp/aspnetcore.mvc.ui": "^1.1.1", + "@abp/bootstrap": "^1.1.1", + "@abp/bootstrap-datepicker": "^1.1.1", + "@abp/datatables.net-bs4": "^1.1.1", + "@abp/font-awesome": "^1.1.1", + "@abp/jquery-form": "^1.1.1", + "@abp/jquery-validation-unobtrusive": "^1.1.1", + "@abp/lodash": "^1.1.1", + "@abp/luxon": "^1.1.1", + "@abp/malihu-custom-scrollbar-plugin": "^1.1.1", + "@abp/select2": "^1.1.1", + "@abp/sweetalert": "^1.1.1", + "@abp/timeago": "^1.1.1", + "@abp/toastr": "^1.1.1" } }, "@abp/bootstrap": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/bootstrap/-/bootstrap-0.4.9.tgz", - "integrity": "sha512-A7lDHo43KnqjINo0xWECx/WzNQe3vqvE3GPv6uXq3KDi+gStQLAFBq3bVcVW/kSxiquDQ/tuCLRpE0qlhoobag==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/bootstrap/-/bootstrap-1.1.1.tgz", + "integrity": "sha512-OIaGJaizhI8UNfy4bnw2xT2Z0QG7BJJrjxOPGepfd4jn/AUi/vFdOpJFWvu2P9PwSzRmn/LuSlr2WONDOdPVWQ==", + "requires": { + "@abp/core": "^1.1.1", + "bootstrap": "^4.3.1" + } + }, + "@abp/bootstrap-datepicker": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/bootstrap-datepicker/-/bootstrap-datepicker-1.1.1.tgz", + "integrity": "sha512-RIQLSrKBu/cTAU2lFenSAoKcMp7wgF4e3nP4/iOu5ZtCgti5vUjMEcqEvBxtlwKTHMXsTG4GtNKqjTwjXMjONQ==", "requires": { - "@abp/core": "^0.4.9", - "bootstrap": "^4.1.1" + "bootstrap-datepicker": "^1.9.0" } }, "@abp/clipboard": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@abp/clipboard/-/clipboard-0.5.1.tgz", - "integrity": "sha512-efOPloVL0moRqGpAMA7DU5o+vzzu7ipGOFcG9nOfDD6uGY9AsOztFc71GwIFrtwADvJSiJHD6OfEIfR++dCT0w==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/clipboard/-/clipboard-1.1.1.tgz", + "integrity": "sha512-O6b7VCAh2mxjkPhgUZYQBhXf0u+dWRECBOYN4KxYiKfk+2xL8X+34Ls7hVLbyuD+4xCcNirMZIG5CnS8auHC/A==", "requires": { - "@abp/core": "^0.4.9", + "@abp/core": "^1.1.1", "clipboard": "^2.0.4" } }, "@abp/core": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/core/-/core-0.4.9.tgz", - "integrity": "sha512-JbibIPDz0w/C9YhexNagMD763qbZCclcHLVlTpKEZUcxzp6rZXwPR6ekh/PnvzD0R7wmHOh+bHl0hs5JSdy3oA==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/core/-/core-1.1.1.tgz", + "integrity": "sha512-OvUG7xRvk8nSqwC1s45YPnTuhC2OWe1AVa1nnC6FVHMH/g1Je7UJwnbu47K7uNS+lDRJUIktNbufYKiwutEjRg==" }, "@abp/datatables.net": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/datatables.net/-/datatables.net-0.4.9.tgz", - "integrity": "sha512-URimiGMFBusEMo9aAadLFCossbYyrN2R6+6YrQ+heqxdheCPmm2wupsXSFoUBHBhD0tzF4rY+OL//oOg3Wtg4g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/datatables.net/-/datatables.net-1.1.1.tgz", + "integrity": "sha512-y76IDBlgc0n1YgQqJ+9cfzXpLwr2arhdIfSmqX+qRXz6GfVNY7e3JijkFSgDKUzKGYo1HZMzgJmDmeNIRwMZsQ==", "requires": { - "@abp/core": "^0.4.9", - "datatables.net": "^1.10.16" + "@abp/core": "^1.1.1", + "datatables.net": "^1.10.20" } }, "@abp/datatables.net-bs4": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/datatables.net-bs4/-/datatables.net-bs4-0.4.9.tgz", - "integrity": "sha512-Dsz0fy2haBz3dl2MLXWjW2bhDBzp1cpWXSXj8TEEzYazjhyoqBy6AiFlEnRCqO49KZD6Ps1cONSRxx4c4On9Lg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/datatables.net-bs4/-/datatables.net-bs4-1.1.1.tgz", + "integrity": "sha512-t40xQIGBMLPZiSbcZHW3AwE8uk+xcl7OitBT1jym0XPKVtgJsHez3ynDE5v/PjHe+ColCG8lTRjRnNoXo5dzDw==", "requires": { - "@abp/datatables.net": "^0.4.9", - "datatables.net-bs4": "^1.10.16" + "@abp/datatables.net": "^1.1.1", + "datatables.net-bs4": "^1.10.20" } }, "@abp/docs": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@abp/docs/-/docs-0.5.1.tgz", - "integrity": "sha512-3pGYnxZxm2kYPfu2EwDF0QinsNTym2cWlYAo7mVUBuReTPMhwHYtgrE7AX3nCVv1iAo6ltW+mebonGt1s5yXzA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/docs/-/docs-1.1.1.tgz", + "integrity": "sha512-z2Dk7EOhBdFo6BYbgccD91PIg37L2ehkgRT4RWf61dRyjPAQgPwwwyVSqApFmKveWPCzreHoTPKbnrU7EzAssw==", "requires": { - "@abp/anchor-js": "^0.5.1", - "@abp/clipboard": "^0.5.1", - "@abp/malihu-custom-scrollbar-plugin": "^0.5.1", - "@abp/popper.js": "^0.5.1", - "@abp/prismjs": "^0.5.1" + "@abp/anchor-js": "^1.1.1", + "@abp/clipboard": "^1.1.1", + "@abp/malihu-custom-scrollbar-plugin": "^1.1.1", + "@abp/popper.js": "^1.1.1", + "@abp/prismjs": "^1.1.1" } }, "@abp/font-awesome": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/font-awesome/-/font-awesome-0.4.9.tgz", - "integrity": "sha512-H4H/PJeSypoq++jP7eqS3y5jpbPIglUByPymHnho7U84gO06tZBUIoK00zyLACUVLzNztWANhmVqyA6OaHQ6xQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/font-awesome/-/font-awesome-1.1.1.tgz", + "integrity": "sha512-6RHbixi7IVWAb3JCHrUmEYD3HmAH4R75Nuo54LvFzATrh4G6gdBONIeDuTo79OTxe4Zhp+WLxeA49Y21kt77mg==", "requires": { - "@abp/core": "^0.4.9", - "font-awesome": "^4.7.0" + "@abp/core": "^1.1.1", + "@fortawesome/fontawesome-free": "^5.11.2" } }, "@abp/jquery": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/jquery/-/jquery-0.4.9.tgz", - "integrity": "sha512-Lte2rfDhcVXRA18mJMyPs0oFoiX/7x10F9cwMyX4+8e9QulA0dg+MjJZ4HIbxRoKvaYPz0AdE5VZ511folFeTg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/jquery/-/jquery-1.1.1.tgz", + "integrity": "sha512-kj4BTtXF0VbCzCqRXnRVEbGndR3F8NlbBhVQN6BQktOuZta3fvx7f2+pSok8vQv0ddmqUFY7FTT2Ei3l4363LQ==", "requires": { - "@abp/core": "^0.4.9", - "jquery": "^3.3.1" + "@abp/core": "^1.1.1", + "jquery": "^3.4.1" } }, "@abp/jquery-form": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/jquery-form/-/jquery-form-0.4.9.tgz", - "integrity": "sha512-T1LzHNO+L/ATSfsaSu9H5rq9oUGjjBrwcJBVQhWRuGbB/Nsv1t4CdCPZHqbYBsn3ji/6T9C80FMdL+s4iOfy8A==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/jquery-form/-/jquery-form-1.1.1.tgz", + "integrity": "sha512-AIIdN36f8xwr4LgiNnBHohJ5tlxh/r+DuDtXcScpZN6GWBE+XgUotN0pZIIva82IxCyUNdDudzgluX9IjI+00w==", "requires": { - "@abp/jquery": "^0.4.9", + "@abp/jquery": "^1.1.1", "jquery-form": "^4.2.2" } }, "@abp/jquery-validation": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/jquery-validation/-/jquery-validation-0.4.9.tgz", - "integrity": "sha512-8C58G9BTYszW6E0EO6NYqXLEbABXt/yGlOqAFCkPiLVfUcoC6uARqkgIgNVnE99ZUeSmluNsYeCczP0Qb0yNzg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/jquery-validation/-/jquery-validation-1.1.1.tgz", + "integrity": "sha512-YvAjIW8epp+ddu01BTUkZWPfEADAvNPJeUrrZ6OpcPWM15Tf+ddr4ATgJ1LCg0Bh5F09iQC855osow3lt8sc7g==", "requires": { - "@abp/jquery": "^0.4.9", - "jquery-validation": "^1.17.0" + "@abp/jquery": "^1.1.1", + "jquery-validation": "^1.19.1" } }, "@abp/jquery-validation-unobtrusive": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-0.4.9.tgz", - "integrity": "sha512-GR62QEPexX4uLcqF3y72V39nlx/70C7ubeaIxS1ZEq+0wGklwAiyq1p19qpB5Cuj2GJL+vFrIig5w+B79P2iAw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-1.1.1.tgz", + "integrity": "sha512-q1b0KG8l3DXUiW8JXdq9l1jR/CwgzrZdxwdKGLB2J/oxHlywQIb7yrjR6WGCshjPpcx2SkOL0j/ZXMIMh533hQ==", "requires": { - "@abp/jquery-validation": "^0.4.9", - "jquery-validation-unobtrusive": "^3.2.9" + "@abp/jquery-validation": "^1.1.1", + "jquery-validation-unobtrusive": "^3.2.11" } }, "@abp/lodash": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/lodash/-/lodash-0.4.9.tgz", - "integrity": "sha512-/itYbXQL145WigKIGpnyWgARml7QGie7xqhC1WZG5iUkdqLx+4YBXesdzGFykthDewiQNz8mB2gPk64d376AAw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/lodash/-/lodash-1.1.1.tgz", + "integrity": "sha512-nH7bRS28Tf4hEXcpKHd1IM+MzYTqX8t3htGmsLX4UESQd52eODYOIldtX6gm3OW1O6ECwW6si/o0M2pTEpQqvg==", "requires": { - "@abp/core": "^0.4.9", - "lodash": "^4.17.10" - }, - "dependencies": { - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - } + "@abp/core": "^1.1.1", + "lodash": "^4.17.15" + } + }, + "@abp/luxon": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/luxon/-/luxon-1.1.1.tgz", + "integrity": "sha512-WNu8JRSb5FDXfcDwjMYyeYeUN48uuDc/I2cdo3xd1rcY+lbmbzxoG9IYOlE8cRHdgX3z82qsZXFs2lcAy0Le2g==", + "requires": { + "luxon": "^1.21.3" } }, "@abp/malihu-custom-scrollbar-plugin": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-0.5.1.tgz", - "integrity": "sha512-i9XorWsWcqb3tbbiwvE4OQCfXXJO38HlkQgvlwzxshB8xDcS9v/JL5fAAvM/Cr66fB2zybsrCseq9QcBXL+yCQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-1.1.1.tgz", + "integrity": "sha512-n4b4QK/L1Czdx0oOpUR/bWjK9VENexfUSV/aMjwzHhDmEFABAmEfhIpudCYDwewGswrd7C9agmBvakv2rwPQeA==", "requires": { - "@abp/core": "^0.4.9", + "@abp/core": "^1.1.1", "malihu-custom-scrollbar-plugin": "^3.1.5" } }, "@abp/popper.js": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@abp/popper.js/-/popper.js-0.5.1.tgz", - "integrity": "sha512-qS97hQQtG78mVPRvmZZtBb7Vu905NpThl9PN3jPWWDiVbOV2/d+BXCTRhTRbfdup3C5cmGdntjaGxfcxlxHt1Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/popper.js/-/popper.js-1.1.1.tgz", + "integrity": "sha512-heR73cqmMsVPNgsPxBYbkvc842R3hEEuDAj4oaXZwVTeWXayU6TdDcGdIrfwMZwW2eWivYNnO0bMOVmuhZKTTQ==", "requires": { - "@abp/core": "^0.4.9", - "popper.js": "^1.14.6" + "@abp/core": "^1.1.1", + "popper.js": "^1.16.0" } }, "@abp/prismjs": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@abp/prismjs/-/prismjs-0.5.1.tgz", - "integrity": "sha512-DxoidAVV8LmtgFonbzGJEfszQDUaHEQc+/bcJtyQCuv5l+uflVnpAH+6W8IAErt5N96a0RJbeV9ZBXjIhqOzpA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/prismjs/-/prismjs-1.1.1.tgz", + "integrity": "sha512-kZh2imqVTMDWmE2v+S4wMsigu/hSyVaz3VvZzGctFNctzC17LeD6z6ymfrtQ5BPJbCxGSpHwOt4/Y8bycPTEuQ==", "requires": { - "@abp/core": "^0.4.9", - "prismjs": "^1.15.0" + "@abp/core": "^1.1.1", + "prismjs": "^1.17.1" } }, "@abp/select2": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/select2/-/select2-0.4.9.tgz", - "integrity": "sha512-AaJHWy9fkP/LqVkruZGfvzgdZXyT96/FpnflUpoNPXjuvVRptkJpd/BQiWjpyjS+qzuZ+m3X9dms0d0lAyFKYA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/select2/-/select2-1.1.1.tgz", + "integrity": "sha512-t0qcJhD+uo2+XWr4nmMQLAx7MRGQUBdZ81YmGty045ReoSaEKQf4haLkzBcMzpBRusiyMQO/PbxjtwMw/xJQTQ==", "requires": { - "@abp/core": "^0.4.9", - "select2": "^4.0.5" + "@abp/core": "^1.1.1", + "select2": "^4.0.12" } }, "@abp/sweetalert": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/sweetalert/-/sweetalert-0.4.9.tgz", - "integrity": "sha512-FaxtELo7Um/uasasbOGi1j1XuBtZ819mUo7+6pMSWj2CSAkg2A/RyT9NhYcX8oyGpv188W5CjZfJ1DM0bRH+pQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/sweetalert/-/sweetalert-1.1.1.tgz", + "integrity": "sha512-V6K/qg7J/bdFmom2kaXYeiLvcmLHFl+MacPX4yYAK2biZdb2pWOkUdmcAzZdOT+UruKfLRhvraVC2uXDySi9NA==", "requires": { - "@abp/core": "^0.4.9", - "sweetalert": "^2.1.0" + "@abp/core": "^1.1.1", + "sweetalert": "^2.1.2" } }, "@abp/timeago": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/timeago/-/timeago-0.4.9.tgz", - "integrity": "sha512-RsAyJCl+rEWQL0q0Nx/ijy+iOBYm5IC5Re6y4SQ1jYF/B4n88GM2PFy6kLrE+HHGl5Z5hkWr5OGXmFem50Y3Wg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/timeago/-/timeago-1.1.1.tgz", + "integrity": "sha512-QYYih/4n6XhCqkRw7fBfyg58T5CHqJHyz7SAfq86RiKAJ4jVtjdSVxj3XKxz8eCb56wZGsO1xXXStI3vdLwwNw==", "requires": { - "@abp/jquery": "^0.4.9", - "timeago": "^1.6.3" + "@abp/jquery": "^1.1.1", + "timeago": "^1.6.7" } }, "@abp/toastr": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@abp/toastr/-/toastr-0.4.9.tgz", - "integrity": "sha512-KjnETa1Og5EIMDw/pIxPoKLTABBcsxBN8BqwIG33ya2+BIRfAI7b0lEMoKK+qmwVwI5dkXZlKFGIUmPtb+zVCw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@abp/toastr/-/toastr-1.1.1.tgz", + "integrity": "sha512-GDewBppm+0FO6kTTy0huczoH9P5q6lFicHFAoEawAMkuWJFW/Ihv/YnEvKGDQwGftuVSWexfqBMN/RZ5YSOiGQ==", "requires": { - "@abp/jquery": "^0.4.9", + "@abp/jquery": "^1.1.1", "toastr": "^2.1.4" } }, - "almond": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/almond/-/almond-0.3.3.tgz", - "integrity": "sha1-oOfJWsdiTWQXtElLHmi/9pMWiiA=" + "@fortawesome/fontawesome-free": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.13.0.tgz", + "integrity": "sha512-xKOeQEl5O47GPZYIMToj6uuA2syyFlq9EMSl2ui0uytjY9xbe8XS0pexNWmxrdcCyNGyDmLyYw5FtKsalBUeOg==" }, "anchor-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/anchor-js/-/anchor-js-4.1.1.tgz", - "integrity": "sha512-c2Wl9F1X0C4jkYKLla1SNE2uI6xJrSKsRC7HCCg4yLNQ5sL5D+tDEWrjRaoTuTlMTqBCnF6kOuR3dx59Erxpvw==" + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/anchor-js/-/anchor-js-4.2.2.tgz", + "integrity": "sha512-Rg1tGaG4K3avYqDh7rOYCE/odWxpUiHStnlKL/bGOt9cl6NjR06zhPGVQcCAjE5PT48oQeHVgqNmLzxh0Kuk4A==" }, "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "requires": { - "ansi-wrap": "^0.1.0" - } + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" }, "ansi-gray": { "version": "0.1.1", @@ -260,16 +269,28 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, "ansi-wrap": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=" }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "requires": { + "buffer-equal": "^1.0.0" + } + }, "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -280,35 +301,89 @@ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" }, + "arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4=", + "requires": { + "make-iterator": "^1.0.0" + } + }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" }, + "arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha1-Onc0X/wc814qkYJWAfnljy4kysQ=", + "requires": { + "make-iterator": "^1.0.0" + } + }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" - }, "array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=" }, + "array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha1-L6dLJnOTccOUe9enrcc74zSz15U=", + "requires": { + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + } + } + }, + "array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + } + } + }, "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==" }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + "array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "requires": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } }, "array-unique": { "version": "0.3.2", @@ -320,11 +395,51 @@ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, + "async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + }, + "async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs=", + "requires": { + "async-done": "^1.2.2" + } + }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, + "bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA=", + "requires": { + "arr-filter": "^1.1.1", + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "array-each": "^1.0.0", + "array-initial": "^1.0.0", + "array-last": "^1.1.1", + "async-done": "^1.2.2", + "async-settle": "^1.0.0", + "now-and-later": "^2.0.0" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -380,15 +495,32 @@ } } }, - "beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=" + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } }, "bootstrap": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.1.3.tgz", - "integrity": "sha512-rDFIzgXcof0jDyjNosjv4Sno77X4KuPeFxG2XZZv1/Kc8DRVGVADdoQyyOVDwPqL36DDmtCQbrpMCqvpPLJQ0w==" + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.4.1.tgz", + "integrity": "sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA==" + }, + "bootstrap-datepicker": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/bootstrap-datepicker/-/bootstrap-datepicker-1.9.0.tgz", + "integrity": "sha512-9rYYbaVOheGYxjOr/+bJCmRPihfy+LkLSg4fIFMT9Od8WwWB/MB50w0JO1eBgKUMbb7PFHQD5uAfI3ArAxZRXA==", + "requires": { + "jquery": ">=1.7.1 <4.0.0" + } }, "brace-expansion": { "version": "1.1.11", @@ -426,6 +558,16 @@ } } }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -442,16 +584,35 @@ "unset-value": "^1.0.0" } }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + } } }, "class-utils": { @@ -476,24 +637,64 @@ } }, "clipboard": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", - "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz", + "integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==", "requires": { "good-listener": "^1.2.2", "select": "^1.1.2", "tiny-emitter": "^2.0.0" } }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=" }, "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=" + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw=", + "requires": { + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } }, "collection-visit": { "version": "1.0.0", @@ -510,47 +711,79 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" }, "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "requires": { + "safe-buffer": "~5.1.1" + } + }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, + "copy-props": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", + "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "requires": { + "each-props": "^1.3.0", + "is-plain-object": "^2.0.1" + } + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, "datatables.net": { - "version": "1.10.19", - "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.10.19.tgz", - "integrity": "sha512-+ljXcI6Pj3PTGy5pesp3E5Dr3x3AV45EZe0o1r0gKENN2gafBKXodVnk2ypKwl2tTmivjxbkiqoWnipTefyBTA==", + "version": "1.10.20", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.10.20.tgz", + "integrity": "sha512-4E4S7tTU607N3h0fZPkGmAtr9mwy462u+VJ6gxYZ8MxcRIjZqHy3Dv1GNry7i3zQCktTdWbULVKBbkAJkuHEnQ==", "requires": { "jquery": ">=1.7" } }, "datatables.net-bs4": { - "version": "1.10.19", - "resolved": "https://registry.npmjs.org/datatables.net-bs4/-/datatables.net-bs4-1.10.19.tgz", - "integrity": "sha512-pgeP17w4aPR7HIxIwuJghfqXULjdg1K6xMUUKDyCERJRSNNK4MRToFfELtIsluLNN555YBK4Kx8nihX5/ZT1Fw==", + "version": "1.10.20", + "resolved": "https://registry.npmjs.org/datatables.net-bs4/-/datatables.net-bs4-1.10.20.tgz", + "integrity": "sha512-kQmMUMsHMOlAW96ztdoFqjSbLnlGZQ63iIM82kHbmldsfYdzuyhbb4hTx6YNBi481WCO3iPSvI6YodNec46ZAw==", "requires": { - "datatables.net": "1.10.19", + "datatables.net": "1.10.20", "jquery": ">=1.7" } }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=" - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -559,17 +792,42 @@ "ms": "2.0.0" } }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "requires": { + "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", "requires": { - "clone": "^1.0.2" + "object-keys": "^1.0.12" } }, "define-property": { @@ -614,30 +872,65 @@ "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" }, - "deprecated": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz", - "integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=" - }, "detect-file": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=" }, - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", "requires": { - "readable-stream": "~1.1.9" + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" } }, "end-of-stream": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", - "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "requires": { - "once": "~1.3.0" + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" } }, "es6-object-assign": { @@ -645,10 +938,25 @@ "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=" }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } }, "expand-brackets": { "version": "2.1.4", @@ -690,6 +998,21 @@ "homedir-polyfill": "^1.0.1" } }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", + "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==" + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -779,15 +1102,22 @@ } }, "fancy-log": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", - "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz", + "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==", "requires": { "ansi-gray": "^0.1.1", "color-support": "^1.1.3", + "parse-node-version": "^1.0.0", "time-stamp": "^1.0.0" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -809,26 +1139,30 @@ } } }, - "find-index": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", - "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=" + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } }, "findup-sync": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", - "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", "requires": { "detect-file": "^1.0.0", - "is-glob": "^3.1.0", + "is-glob": "^4.0.0", "micromatch": "^3.0.4", "resolve-dir": "^1.0.1" } }, "fined": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.0.tgz", - "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", "requires": { "expand-tilde": "^2.0.2", "is-plain-object": "^2.0.3", @@ -837,20 +1171,19 @@ "parse-filepath": "^1.0.1" } }, - "first-chunk-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", - "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=" - }, "flagged-respawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.0.tgz", - "integrity": "sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==" }, - "font-awesome": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", - "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } }, "for-in": { "version": "1.0.2", @@ -873,84 +1206,579 @@ "map-cache": "^0.2.2" } }, - "fs.realpath": { + "fs-mkdirp-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "gaze": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", - "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", "requires": { - "globule": "~0.1.0" + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" } }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "glob": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", - "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", + "fsevents": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", + "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", + "optional": true, "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^2.0.1", - "once": "^1.3.0" - } - }, - "glob-stream": { - "version": "3.1.18", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", - "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", - "requires": { - "glob": "^4.3.1", - "glob2base": "^0.0.12", - "minimatch": "^2.0.1", - "ordered-read-streams": "^0.1.0", - "through2": "^0.6.1", - "unique-stream": "^1.0.0" + "bindings": "^1.5.0", + "nan": "^2.12.1", + "node-pre-gyp": "*" }, "dependencies": { - "readable-stream": { - "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "abbrev": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "optional": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "optional": true, "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } - } + }, + "chownr": { + "version": "1.1.4", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "debug": { + "version": "3.2.6", + "bundled": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.7", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.6.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.6", + "bundled": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.3", + "bundled": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "bundled": true, + "optional": true + }, + "minipass": { + "version": "2.9.0", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.9.0" + } + }, + "mkdirp": { + "version": "0.5.3", + "bundled": true, + "optional": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "needle": { + "version": "2.3.3", + "bundled": true, + "optional": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.14.0", + "bundled": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + } + }, + "nopt": { + "version": "4.0.3", + "bundled": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.8", + "bundled": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "bundled": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.7.1", + "bundled": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "optional": true + }, + "semver": { + "version": "5.7.1", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "4.4.13", + "bundled": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "yallist": { + "version": "3.1.1", + "bundled": true, + "optional": true + } } }, - "glob-watcher": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", - "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", "requires": { - "gaze": "^0.5.1" + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" } }, - "glob2base": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", - "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", + "glob-watcher": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", + "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", "requires": { - "find-index": "^0.1.1" + "anymatch": "^2.0.0", + "async-done": "^1.2.0", + "chokidar": "^2.0.0", + "is-negated-glob": "^1.0.0", + "just-debounce": "^1.0.0", + "object.defaults": "^1.1.0" } }, "global-modules": { @@ -975,51 +1803,10 @@ "which": "^1.2.14" } }, - "globule": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", - "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", - "requires": { - "glob": "~3.1.21", - "lodash": "~1.0.1", - "minimatch": "~0.2.11" - }, - "dependencies": { - "glob": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", - "requires": { - "graceful-fs": "~1.2.0", - "inherits": "1", - "minimatch": "~0.2.11" - } - }, - "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=" - }, - "inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=" - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - } - } - }, "glogg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.1.tgz", - "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", + "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==", "requires": { "sparkles": "^1.0.0" } @@ -1033,56 +1820,54 @@ } }, "graceful-fs": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", - "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", - "requires": { - "natives": "^1.1.0" - } + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" }, "gulp": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", - "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", - "requires": { - "archy": "^1.0.0", - "chalk": "^1.0.0", - "deprecated": "^0.0.1", - "gulp-util": "^3.0.0", - "interpret": "^1.0.0", - "liftoff": "^2.1.0", - "minimist": "^1.1.0", - "orchestrator": "^0.3.0", - "pretty-hrtime": "^1.0.0", - "semver": "^4.1.0", - "tildify": "^1.0.0", - "v8flags": "^2.0.2", - "vinyl-fs": "^0.3.0" - } - }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", + "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", + "requires": { + "glob-watcher": "^5.0.3", + "gulp-cli": "^2.2.0", + "undertaker": "^1.2.1", + "vinyl-fs": "^3.0.0" + }, + "dependencies": { + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "gulp-cli": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.2.0.tgz", + "integrity": "sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA==", + "requires": { + "ansi-colors": "^1.0.1", + "archy": "^1.0.0", + "array-sort": "^1.0.0", + "color-support": "^1.1.3", + "concat-stream": "^1.6.0", + "copy-props": "^2.0.1", + "fancy-log": "^1.3.2", + "gulplog": "^1.0.0", + "interpret": "^1.1.0", + "isobject": "^3.0.1", + "liftoff": "^3.1.0", + "matchdep": "^2.0.0", + "mute-stdout": "^1.0.0", + "pretty-hrtime": "^1.0.0", + "replace-homedir": "^1.0.0", + "semver-greatest-satisfied-range": "^1.1.0", + "v8flags": "^3.0.1", + "yargs": "^7.1.0" + } + } } }, "gulplog": { @@ -1093,21 +1878,10 @@ "glogg": "^1.0.0" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "requires": { - "sparkles": "^1.0.0" - } + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "has-value": { "version": "1.0.0", @@ -1139,13 +1913,18 @@ } }, "homedir-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", - "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", "requires": { "parse-passwd": "^1.0.0" } }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1156,9 +1935,9 @@ } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", @@ -1166,9 +1945,14 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", + "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" }, "is-absolute": { "version": "1.0.0", @@ -1197,6 +1981,19 @@ } } }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "^1.0.0" + } + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -1247,14 +2044,27 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "^2.1.1" } }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=" + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -1302,15 +2112,20 @@ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=" + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" }, "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -1323,9 +2138,9 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "jquery": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", - "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", + "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" }, "jquery-form": { "version": "4.2.2", @@ -1341,154 +2156,103 @@ "integrity": "sha1-BvAzXxbjU6aV5yBr9QUDy1I6buU=" }, "jquery-validation": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.18.0.tgz", - "integrity": "sha512-+MK0pvoegfLrJnwtCU6tx305Vgp9HWevpmdVwDf5TthmINkn0wqqLD0bpa75EERlHsBBjMmza//ppx5ZQPnW3Q==", - "requires": { - "jquery": "^1.7 || ^2.0 || ^3.1" - } + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.19.1.tgz", + "integrity": "sha512-QNnrZBqSltWUEJx+shOY5WtfrIb0gWmDjFfQP8rZKqMMSfpRSwEkSqhfHPvDfkObD8Hnv5KHSYI8yg73sVFdqA==" }, "jquery-validation-unobtrusive": { - "version": "3.2.10", - "resolved": "https://registry.npmjs.org/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.2.10.tgz", - "integrity": "sha512-z9ZBP/HslaGNKzFSpfLNJoFm2iqPJfE6CKM0H5e9LmKnYTFxErvCFQZomOLiTmLmZi8Wi/otW38cEXExVDha0w==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.2.11.tgz", + "integrity": "sha512-3FQPllaWdD+Aq55zJLGSW39+eXPDz1HhwAvrSwYi8zHQ8DVcu5IJ1HVeTiCl0BnCnrIBvfFU3zEB/DrGdcoRIQ==", "requires": { "jquery": ">=1.8", "jquery-validation": ">=1.16" } }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" + }, + "just-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", + "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=" + }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, - "liftoff": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", - "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", + "last-run": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", + "integrity": "sha1-RblpQsF7HHnHchmCWbqUO+v4yls=", "requires": { - "extend": "^3.0.0", - "findup-sync": "^2.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" + "default-resolution": "^2.0.0", + "es6-weak-map": "^2.0.1" } }, - "lodash": { - "version": "1.0.2", - "resolved": "http://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", - "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=" - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" - }, - "lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=" - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=" - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" - }, - "lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=" - }, - "lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=" - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=" - }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "requires": { - "lodash._root": "^3.0.0" + "readable-stream": "^2.0.5" } }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" + "invert-kv": "^1.0.0" } }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "requires": { + "flush-write-stream": "^1.0.2" + } }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "liftoff": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", + "integrity": "sha512-DlIPlJUkCV0Ips2zf2pJP0unEoT1kwYhiiPUGF3s/jtxTCjziNLoiVVh+jqWOWeFi6mmwQ5fNxvAUyPad4Dfog==", "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" + "extend": "^3.0.0", + "findup-sync": "^3.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" } }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" } }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "luxon": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.22.2.tgz", + "integrity": "sha512-vq6eSaOOw1fKob+JXwfu0e3/UFUT4G4HTFRJab7dch8J1OdOGW/vXqCiJsY7rm2In+5gKNYx0EtnYT0Tc5V4Qw==" }, "make-iterator": { "version": "1.0.1", @@ -1519,43 +2283,43 @@ "object-visit": "^1.0.0" } }, - "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "matchdep": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", + "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", "requires": { - "readable-stream": "^2.0.1" + "findup-sync": "^2.0.0", + "micromatch": "^3.0.4", + "resolve": "^1.4.0", + "stack-trace": "0.0.10" }, "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "requires": { - "safe-buffer": "~5.1.0" + "is-extglob": "^2.1.0" } } } }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -1577,22 +2341,17 @@ } }, "minimatch": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", - "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "^1.0.0" + "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -1608,33 +2367,21 @@ } } }, - "mkdirp": { - "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "requires": { - "duplexer2": "0.0.2" - } + "mute-stdout": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", + "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==" + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "optional": true }, "nanomatch": { "version": "1.2.13", @@ -1654,15 +2401,42 @@ "to-regex": "^3.0.1" } }, - "natives": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.5.tgz", - "integrity": "sha512-1pJ+02gl2KJgCPFtpZGtuD4lGSJnIZvvFHCQTOeDRMSXjfu2GmYWuhI8NFMA4W2I5NNFRbfy/YCiVt4CgNpP8A==" + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" }, - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "now-and-later": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", + "integrity": "sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==", + "requires": { + "once": "^1.3.2" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-copy": { "version": "0.1.0", @@ -1692,12 +2466,28 @@ } } }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "requires": { - "isobject": "^3.0.0" + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" } }, "object.defaults": { @@ -1728,33 +2518,38 @@ "isobject": "^3.0.1" } }, - "once": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "object.reduce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.reduce/-/object.reduce-1.0.1.tgz", + "integrity": "sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60=", "requires": { - "wrappy": "1" + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" } }, - "orchestrator": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.8.tgz", - "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "end-of-stream": "~0.1.5", - "sequencify": "~0.0.7", - "stream-consume": "~0.1.0" + "wrappy": "1" } }, "ordered-read-streams": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz", - "integrity": "sha1-/VZamvjrRHO6abbtijQ1LLVS8SY=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "requires": { + "readable-stream": "^2.0.1" + } }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } }, "parse-filepath": { "version": "1.0.2", @@ -1766,6 +2561,19 @@ "path-root": "^0.1.1" } }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==" + }, "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", @@ -1785,6 +2593,19 @@ "util": "^0.10.3" } }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1808,10 +2629,38 @@ "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=" }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, "popper.js": { - "version": "1.14.6", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.6.tgz", - "integrity": "sha512-AGwHGQBKumlk/MDfrSOf0JHhJCImdDMcGNoqKmKkU+68GFazv3CQ6q9r7Ja1sKDZmYWTckY/uLyEznheTDycnA==" + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, "posix-character-classes": { "version": "0.1.1", @@ -1824,9 +2673,9 @@ "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=" }, "prismjs": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.15.0.tgz", - "integrity": "sha512-Lf2JrFYx8FanHrjoV5oL8YHCclLQgbJcVZR+gikGGMqz6ub5QVWDTM6YIwm3BuPxM/LOV+rKns3LssXNLIf+DA==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.19.0.tgz", + "integrity": "sha512-IVFtbW9mCWm9eOIaEkNyo2Vl4NnEifis2GQ7/MLRG5TQe6t+4Sj9J5QWI9i3v+SS43uZBlCAOn+zYTVYQcPXJw==", "requires": { "clipboard": "^2.0.0" } @@ -1837,24 +2686,75 @@ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "promise-polyfill": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz", "integrity": "sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=" }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, "readable-stream": { - "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" } }, "rechoir": { @@ -1874,6 +2774,30 @@ "safe-regex": "^1.1.0" } }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "requires": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "requires": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", @@ -1885,16 +2809,36 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" + }, + "replace-homedir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-1.0.0.tgz", + "integrity": "sha1-6H9tUTuSjd6AgmDBK+f+xv9ueYw=", + "requires": { + "homedir-polyfill": "^1.0.1", + "is-absolute": "^1.0.0", + "remove-trailing-separator": "^1.1.0" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", "requires": { - "path-parse": "^1.0.5" + "path-parse": "^1.0.6" } }, "resolve-dir": { @@ -1906,6 +2850,14 @@ "global-modules": "^1.0.0" } }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "requires": { + "value-or-function": "^3.0.0" + } + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -1917,34 +2869,11 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" }, "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "requires": { - "glob": "^7.0.5" - }, - "dependencies": { - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - } + "glob": "^7.1.3" } }, "safe-buffer": { @@ -1966,28 +2895,32 @@ "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" }, "select2": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/select2/-/select2-4.0.5.tgz", - "integrity": "sha1-eqxQaSVhmFs007guxV4ib4lg1Ao=", - "requires": { - "almond": "~0.3.1", - "jquery-mousewheel": "~3.1.13" - } + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/select2/-/select2-4.0.13.tgz", + "integrity": "sha512-1JeB87s6oN/TDxQQYCvS5EFoQyvV6eYMZZ0AeA4tdFDYWN3BAGZ8npr17UBFddU0lgAt3H0yjX3X6/ekOj1yjw==" }, "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=" + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "semver-greatest-satisfied-range": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz", + "integrity": "sha1-E+jCZYq5aRywzXEJMkAoDTb3els=", + "requires": { + "sver-compat": "^1.5.0" + } }, - "sequencify": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz", - "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=" + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -2005,11 +2938,6 @@ } } }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -2113,11 +3041,11 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", "requires": { - "atob": "^2.1.1", + "atob": "^2.1.2", "decode-uri-component": "^0.2.0", "resolve-url": "^0.2.1", "source-map-url": "^0.4.0", @@ -2134,6 +3062,34 @@ "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==" }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -2142,6 +3098,11 @@ "extend-shallow": "^3.0.0" } }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -2161,15 +3122,33 @@ } } }, - "stream-consume": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", - "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==" + "stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==" + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } }, "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } }, "strip-ansi": { "version": "3.0.1", @@ -2180,72 +3159,47 @@ } }, "strip-bom": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz", - "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "requires": { - "first-chunk-stream": "^1.0.0", "is-utf8": "^0.2.0" } }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + "sver-compat": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", + "integrity": "sha1-PPh9/rTQe0o/FIJ7wYaz/QxkXNg=", + "requires": { + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } }, "sweetalert": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sweetalert/-/sweetalert-2.1.0.tgz", - "integrity": "sha512-9YKj0SvjKyBfRWco50UOsIbXVeifYbxzT9Qda7EsqC01eafHGCSG0IR7g942ufjzt7lnwO8ZZBwr6emXv2fQrg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/sweetalert/-/sweetalert-2.1.2.tgz", + "integrity": "sha512-iWx7X4anRBNDa/a+AdTmvAzQtkN1+s4j/JJRWlHpYE8Qimkohs8/XnFcWeYHH2lMA8LRCa5tj2d244If3S/hzA==", "requires": { "es6-object-assign": "^1.1.0", "promise-polyfill": "^6.0.2" } }, "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "requires": { - "readable-stream": "^2.1.5", + "readable-stream": "~2.3.6", "xtend": "~4.0.1" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, - "tildify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", - "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", + "through2-filter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", "requires": { - "os-homedir": "^1.0.0" + "through2": "~2.0.0", + "xtend": "~4.0.0" } }, "time-stamp": { @@ -2254,17 +3208,26 @@ "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=" }, "timeago": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/timeago/-/timeago-1.6.3.tgz", - "integrity": "sha512-x97d1X1KsNapWJTgCOOAy/59XagYu2WsDTAH/yvPsWi5bqtGbLPaVZBv3HZ3jTpakHR+JGGyrI9qC0yuvIAvnQ==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/timeago/-/timeago-1.6.7.tgz", + "integrity": "sha512-FikcjN98+ij0siKH4VO4dZ358PR3oDDq4Vdl1+sN9gWz1/+JXGr3uZbUShYH/hL7bMhcTpPbplJU5Tej4b4jbQ==", "requires": { - "jquery": ">=1.2.3" + "jquery": ">=1.5.0 <4.0" } }, "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, + "to-absolute-glob": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", - "integrity": "sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==" + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + } }, "to-object-path": { "version": "0.3.0", @@ -2304,6 +3267,14 @@ "repeat-string": "^1.6.1" } }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "requires": { + "through2": "^2.0.3" + } + }, "toastr": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz", @@ -2312,47 +3283,61 @@ "jquery": ">=1.12.0" } }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" }, + "undertaker": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", + "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "requires": { + "arr-flatten": "^1.0.1", + "arr-map": "^2.0.0", + "bach": "^1.0.0", + "collection-map": "^1.0.0", + "es6-weak-map": "^2.0.1", + "last-run": "^1.1.0", + "object.defaults": "^1.0.0", + "object.reduce": "^1.0.0", + "undertaker-registry": "^1.0.0" + } + }, + "undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha1-XkvaMI5KiirlhPm5pDWaSZglzFA=" + }, "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "set-value": "^2.0.1" } }, "unique-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz", - "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", + "requires": { + "json-stable-stringify-without-jsonify": "^1.0.1", + "through2-filter": "^3.0.0" + } }, "unset-value": { "version": "1.0.0", @@ -2387,14 +3372,14 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" } } }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" + }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", @@ -2405,17 +3390,19 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, - "user-home": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", - "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=" - }, "util": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", "requires": { "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } } }, "util-deprecate": { @@ -2424,72 +3411,76 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "v8flags": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", - "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", + "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "requires": { - "user-home": "^1.1.1" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=" + }, "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", + "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" } }, "vinyl-fs": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz", - "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", - "requires": { - "defaults": "^1.0.0", - "glob-stream": "^3.1.5", - "glob-watcher": "^0.0.6", - "graceful-fs": "^3.0.0", - "mkdirp": "^0.5.0", - "strip-bom": "^1.0.0", - "through2": "^0.6.1", - "vinyl": "^0.4.0" - }, - "dependencies": { - "clone": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", - "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=" - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - } - }, - "vinyl": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", - "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", - "requires": { - "clone": "^0.2.0", - "clone-stats": "^0.0.1" - } - } + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "requires": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "requires": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" } }, "which": { @@ -2500,15 +3491,62 @@ "isexe": "^2.0.0" } }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=" + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yargs": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", + "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "requires": { + "camelcase": "^3.0.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^1.4.0", + "read-pkg-up": "^1.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^1.0.2", + "which-module": "^1.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^5.0.0" + } + }, + "yargs-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", + "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "requires": { + "camelcase": "^3.0.0" + } } } } diff --git a/modules/docs/app/VoloDocs.Web/package.json b/modules/docs/app/VoloDocs.Web/package.json index fca480e355..b711db27ba 100644 --- a/modules/docs/app/VoloDocs.Web/package.json +++ b/modules/docs/app/VoloDocs.Web/package.json @@ -6,4 +6,4 @@ "@abp/aspnetcore.mvc.ui.theme.basic": "^1.0.2", "@abp/docs": "^1.0.2" } -} \ No newline at end of file +} diff --git a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs index 57ade1bb4b..1000091b65 100644 --- a/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs +++ b/modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/Documents/DocumentAdminAppService.cs @@ -4,11 +4,13 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Newtonsoft.Json; +using Volo.Abp; using Volo.Abp.Application.Services; using Volo.Abp.Caching; using Volo.Docs.Documents; using Volo.Docs.Documents.FullSearch.Elastic; using Volo.Docs.Projects; +using Volo.Extensions; namespace Volo.Docs.Admin.Documents { @@ -38,15 +40,19 @@ namespace Volo.Docs.Admin.Documents { var project = await _projectRepository.GetAsync(input.ProjectId); - var navigationFile = await GetDocumentAsync( + var navigationDocument = await GetDocumentAsync( project, project.NavigationDocumentName, input.LanguageCode, input.Version ); - var nav = JsonConvert.DeserializeObject(navigationFile.Content); - var leafs = nav.Items.GetAllNodes(x => x.Items) + if (!JsonConvertExtensions.TryDeserializeObject(navigationDocument.Content, out var navigation)) + { + throw new UserFriendlyException($"Cannot validate navigation file '{project.NavigationDocumentName}' for the project {project.Name}."); + } + + var leafs = navigation.Items.GetAllNodes(x => x.Items) .Where(x => x.IsLeaf && !x.Path.IsNullOrWhiteSpace()) .ToList(); diff --git a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs index 9863995d15..61774bfb45 100644 --- a/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs +++ b/modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs @@ -3,13 +3,17 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Nest; using Newtonsoft.Json; +using Volo.Abp; using Volo.Abp.Caching; using Volo.Docs.Documents.FullSearch.Elastic; using Volo.Docs.Projects; +using Volo.Extensions; namespace Volo.Docs.Documents { @@ -24,6 +28,10 @@ namespace Volo.Docs.Documents protected IHostEnvironment HostEnvironment { get; } private readonly IDocumentFullSearch _documentFullSearch; private readonly DocsElasticSearchOptions _docsElasticSearchOptions; + private readonly IConfiguration _configuration; + private readonly TimeSpan _cacheTimeout; + private readonly TimeSpan _documentResourceAbsoluteExpiration; + private readonly TimeSpan _documentResourceSlidingExpiration; public DocumentAppService( IProjectRepository projectRepository, @@ -32,9 +40,10 @@ namespace Volo.Docs.Documents IDistributedCache languageCache, IDistributedCache resourceCache, IDistributedCache documentUpdateCache, - IHostEnvironment hostEnvironment, - IDocumentFullSearch documentFullSearch, - IOptions docsElasticSearchOptions) + IHostEnvironment hostEnvironment, + IDocumentFullSearch documentFullSearch, + IOptions docsElasticSearchOptions, + IConfiguration configuration) { _projectRepository = projectRepository; _documentRepository = documentRepository; @@ -44,7 +53,11 @@ namespace Volo.Docs.Documents DocumentUpdateCache = documentUpdateCache; HostEnvironment = hostEnvironment; _documentFullSearch = documentFullSearch; + _configuration = configuration; _docsElasticSearchOptions = docsElasticSearchOptions.Value; + _cacheTimeout = GetCacheTimeout(); + _documentResourceAbsoluteExpiration = GetDocumentResourceAbsoluteExpirationTimeout(); + _documentResourceSlidingExpiration = GetDocumentResourceSlidingExpirationTimeout(); } public virtual async Task GetAsync(GetDocumentInput input) @@ -82,7 +95,10 @@ namespace Volo.Docs.Documents input.Version ); - var navigationNode = JsonConvert.DeserializeObject(navigationDocument.Content); + if (!JsonConvertExtensions.TryDeserializeObject(navigationDocument.Content, out var navigationNode)) + { + throw new UserFriendlyException($"Cannot validate navigation file '{project.NavigationDocumentName}' for the project {project.Name}."); + } var leafs = navigationNode.Items.GetAllNodes(x => x.Items) .Where(x => !x.Path.IsNullOrWhiteSpace()) @@ -127,9 +143,8 @@ namespace Volo.Docs.Documents GetResourceAsync, () => new DistributedCacheEntryOptions { - //TODO: Configurable? - AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6), - SlidingExpiration = TimeSpan.FromMinutes(30) + AbsoluteExpirationRelativeToNow = _documentResourceAbsoluteExpiration, + SlidingExpiration = _documentResourceSlidingExpiration } ); } @@ -173,11 +188,16 @@ namespace Volo.Docs.Documents input.Version ); - return JsonConvert.DeserializeObject(document.Content); + if (!JsonConvertExtensions.TryDeserializeObject(document.Content, out var documentParameters)) + { + throw new UserFriendlyException($"Cannot validate document parameters file '{project.ParametersDocumentName}' for the project {project.Name}."); + } + + return documentParameters; } catch (DocumentNotFoundException) { - Logger.LogWarning($"Parameter file ({project.ParametersDocumentName}) not found."); + Logger.LogWarning($"Parameter file ({project.ParametersDocumentName}) not found!"); return new DocumentParametersDto(); } } @@ -190,48 +210,23 @@ namespace Volo.Docs.Documents { version = string.IsNullOrWhiteSpace(version) ? project.LatestVersionBranchName : version; - async Task GetDocumentAsync(Document oldDocument = null) - { - Logger.LogInformation($"Not found in the cache. Requesting {documentName} from the source..."); - - var source = _documentStoreFactory.Create(project.DocumentStoreType); - var sourceDocument = await source.GetDocumentAsync(project, documentName, languageCode, version, oldDocument?.LastSignificantUpdateTime); - - await _documentRepository.DeleteAsync(project.Id, sourceDocument.Name, sourceDocument.LanguageCode, sourceDocument.Version); - await _documentRepository.InsertAsync(sourceDocument, true); - - Logger.LogInformation($"Document retrieved: {documentName}"); - - var cacheKey = $"DocumentUpdateInfo{sourceDocument.ProjectId}#{sourceDocument.Name}#{sourceDocument.LanguageCode}#{sourceDocument.Version}"; - await DocumentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo - { - Name = sourceDocument.Name, - CreationTime = sourceDocument.CreationTime, - LastUpdatedTime = sourceDocument.LastUpdatedTime, - LastSignificantUpdateTime = sourceDocument.LastSignificantUpdateTime - }); - - return CreateDocumentWithDetailsDto(project, sourceDocument); - } - if (HostEnvironment.IsDevelopment()) { - return await GetDocumentAsync(); + return await GetDocumentAsync(documentName, project, languageCode, version); } var document = await _documentRepository.FindAsync(project.Id, documentName, languageCode, version); if (document == null) { - return await GetDocumentAsync(); + return await GetDocumentAsync(documentName, project, languageCode, version); } //Only the latest version (dev) of the document needs to update the cache. if (!project.LatestVersionBranchName.IsNullOrWhiteSpace() && document.Version == project.LatestVersionBranchName && - //TODO: Configurable cache time? - document.LastCachedTime + TimeSpan.FromHours(2) < DateTime.Now) + document.LastCachedTime + _cacheTimeout < DateTime.Now) { - return await GetDocumentAsync(document); + return await GetDocumentAsync(documentName, project, languageCode, version, document); } var cacheKey = $"DocumentUpdateInfo{document.ProjectId}#{document.Name}#{document.LanguageCode}#{document.Version}"; @@ -253,5 +248,62 @@ namespace Volo.Docs.Documents documentDto.Contributors = ObjectMapper.Map, List>(document.Contributors); return documentDto; } + + private async Task GetDocumentAsync(string documentName, Project project, string languageCode, string version, Document oldDocument = null) + { + Logger.LogInformation($"Not found in the cache. Requesting {documentName} from the source..."); + + var source = _documentStoreFactory.Create(project.DocumentStoreType); + var sourceDocument = await source.GetDocumentAsync(project, documentName, languageCode, version, oldDocument?.LastSignificantUpdateTime); + + await _documentRepository.DeleteAsync(project.Id, sourceDocument.Name, sourceDocument.LanguageCode, sourceDocument.Version); + await _documentRepository.InsertAsync(sourceDocument, true); + + Logger.LogInformation($"Document retrieved: {documentName}"); + + var cacheKey = $"DocumentUpdateInfo{sourceDocument.ProjectId}#{sourceDocument.Name}#{sourceDocument.LanguageCode}#{sourceDocument.Version}"; + await DocumentUpdateCache.SetAsync(cacheKey, new DocumentUpdateInfo + { + Name = sourceDocument.Name, + CreationTime = sourceDocument.CreationTime, + LastUpdatedTime = sourceDocument.LastUpdatedTime, + LastSignificantUpdateTime = sourceDocument.LastSignificantUpdateTime + }); + + return CreateDocumentWithDetailsDto(project, sourceDocument); + } + + private TimeSpan GetCacheTimeout() + { + var value = _configuration["Volo.Docs:DocumentCacheTimeoutInterval"]; + if (value.IsNullOrEmpty()) + { + return TimeSpan.FromHours(6); + } + + return TimeSpan.Parse(value); + } + + private TimeSpan GetDocumentResourceAbsoluteExpirationTimeout() + { + var value = _configuration["Volo.Docs:DocumentResource.AbsoluteExpirationRelativeToNow"]; + if (value.IsNullOrEmpty()) + { + return TimeSpan.FromHours(6); + } + + return TimeSpan.Parse(value); + } + + private TimeSpan GetDocumentResourceSlidingExpirationTimeout() + { + var value = _configuration["Volo.Docs:DocumentResource.SlidingExpiration"]; + if (value.IsNullOrEmpty()) + { + return TimeSpan.FromMinutes(30); + } + + return TimeSpan.Parse(value); + } } } \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainConsts.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainConsts.cs new file mode 100644 index 0000000000..ff6bd56ee4 --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainConsts.cs @@ -0,0 +1,7 @@ +namespace Volo.Docs +{ + public class DocsDomainConsts + { + public static string LanguageConfigFileName = "docs-langs.json"; + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs index 3fa341a848..cc0f03a60a 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentSource.cs @@ -4,11 +4,13 @@ using System.IO; using System.Security; using System.Threading.Tasks; using Newtonsoft.Json; +using Volo.Abp; using Volo.Abp.Domain.Services; using Volo.Abp.IO; using Volo.Docs.Documents; using Volo.Docs.FileSystem.Projects; using Volo.Docs.Projects; +using Volo.Extensions; namespace Volo.Docs.FileSystem.Documents { @@ -22,7 +24,7 @@ namespace Volo.Docs.FileSystem.Documents var path = Path.Combine(projectFolder, languageCode, documentName); CheckDirectorySecurity(projectFolder, path); - + var content = await FileHelper.ReadAllTextAsync(path); var localDirectory = ""; @@ -55,10 +57,15 @@ namespace Volo.Docs.FileSystem.Documents public async Task GetLanguageListAsync(Project project, string version) { - var path = Path.Combine(project.GetFileSystemPath(), "docs-langs.json"); - var configAsJson = await FileHelper.ReadAllTextAsync(path); + var path = Path.Combine(project.GetFileSystemPath(), DocsDomainConsts.LanguageConfigFileName); + var configJsonContent = await FileHelper.ReadAllTextAsync(path); + + if (!JsonConvertExtensions.TryDeserializeObject(configJsonContent, out var languageConfig)) + { + throw new UserFriendlyException($"Cannot validate language config file '{DocsDomainConsts.LanguageConfigFileName}' for the project {project.Name}."); + } - return JsonConvert.DeserializeObject(configAsJson); + return languageConfig; } public async Task GetResource(Project project, string resourceName, string languageCode, string version) diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs index 3e287789e9..a6285389de 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentSource.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using Volo.Abp.Domain.Services; using Volo.Docs.Documents; using Volo.Docs.GitHub.Projects; using Volo.Docs.Projects; -using Newtonsoft.Json.Linq; using Octokit; +using Volo.Abp; +using Volo.Extensions; using Project = Volo.Docs.Projects.Project; namespace Volo.Docs.GitHub.Documents @@ -55,11 +55,11 @@ namespace Volo.Docs.GitHub.Documents var lastSignificantUpdateTime = !isNavigationDocument && !isParameterDocument && version == project.LatestVersionBranchName ? await GetLastSignificantUpdateTime( fileCommits, - project, + project, project.GetGitHubInnerUrl(languageCode, documentName), lastKnownSignificantUpdateTime, documentCreationTime - ) ?? lastKnownSignificantUpdateTime + ) ?? lastKnownSignificantUpdateTime : null; var document = new Document(GuidGenerator.Create(), @@ -179,11 +179,16 @@ namespace Volo.Docs.GitHub.Documents var rootUrl = project.GetGitHubUrl(version); var userAgent = project.GetGithubUserAgentOrNull(); - var url = CalculateRawRootUrl(rootUrl) + "docs-langs.json"; + var url = CalculateRawRootUrl(rootUrl) + DocsDomainConsts.LanguageConfigFileName; var configAsJson = await DownloadWebContentAsStringAsync(url, token, userAgent); - return JsonConvert.DeserializeObject(configAsJson); + if (!JsonConvertExtensions.TryDeserializeObject(configAsJson, out var languageConfig)) + { + throw new UserFriendlyException($"Cannot validate language config file '{DocsDomainConsts.LanguageConfigFileName}' for the project {project.Name} - v{version}."); + } + + return languageConfig; } private async Task> GetReleasesAsync(Project project) @@ -199,8 +204,14 @@ namespace Volo.Docs.GitHub.Documents var url = project.GetGitHubUrl(); var ownerName = GetOwnerNameFromUrl(url); var repositoryName = GetRepositoryNameFromUrl(url); - return await _githubRepositoryManager.GetFileCommitsAsync(ownerName, repositoryName, - version, filename, project.GetGitHubAccessTokenOrNull()); + + return await _githubRepositoryManager.GetFileCommitsAsync( + ownerName, + repositoryName, + version, + filename, + project.GetGitHubAccessTokenOrNull() + ); } protected virtual string GetOwnerNameFromUrl(string url) @@ -212,7 +223,7 @@ namespace Volo.Docs.GitHub.Documents } catch (Exception) { - throw new Exception($"Github url is not valid: {url}"); + throw new Exception($"GitHub url is not valid: {url}"); } } diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubRepositoryManager.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubRepositoryManager.cs index 4627800832..ef21353236 100644 --- a/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubRepositoryManager.cs +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubRepositoryManager.cs @@ -23,54 +23,25 @@ namespace Volo.Docs.GitHub.Documents public async Task GetFileRawStringContentAsync(string rawUrl, string token, string userAgent) { - var httpClient = _clientFactory.CreateClient(HttpClientName); - if (!token.IsNullOrWhiteSpace()) - { - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", token); - } - - if (!userAgent.IsNullOrWhiteSpace()) - { - httpClient.DefaultRequestHeaders.Add("User-Agent", userAgent); - } - + using var httpClient = CreateHttpClient(token, userAgent); return await httpClient.GetStringAsync(new Uri(rawUrl)); } public async Task GetFileRawByteArrayContentAsync(string rawUrl, string token, string userAgent) { - var httpClient = _clientFactory.CreateClient(HttpClientName); - if (!token.IsNullOrWhiteSpace()) - { - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", token); - } - - if (!userAgent.IsNullOrWhiteSpace()) - { - httpClient.DefaultRequestHeaders.Add("User-Agent", userAgent); - } - + using var httpClient = CreateHttpClient(token, userAgent); return await httpClient.GetByteArrayAsync(new Uri(rawUrl)); } - + public async Task> GetReleasesAsync(string name, string repositoryName, string token) { - var client = token.IsNullOrWhiteSpace() - ? new GitHubClient(new ProductHeaderValue(name)) - : new GitHubClient(new ProductHeaderValue(name), new InMemoryCredentialStore(new Credentials(token))); - - return (await client - .Repository - .Release - .GetAll(name, repositoryName)).ToList(); + var client = GetGitHubClient(name, token); + return await client.Repository.Release.GetAll(name, repositoryName); } public async Task> GetFileCommitsAsync(string name, string repositoryName, string version, string filename, string token) { - var client = token.IsNullOrWhiteSpace() - ? new GitHubClient(new ProductHeaderValue(name)) - : new GitHubClient(new ProductHeaderValue(name), new InMemoryCredentialStore(new Credentials(token))); - + var client = GetGitHubClient(name, token); var repo = await client.Repository.Get(name, repositoryName); var request = new CommitRequest { Path = filename, Sha = version }; return await client.Repository.Commit.GetAll(repo.Id, request); @@ -78,12 +49,32 @@ namespace Volo.Docs.GitHub.Documents public async Task GetSingleCommitsAsync(string name, string repositoryName, string sha, string token) { - var client = token.IsNullOrWhiteSpace() - ? new GitHubClient(new ProductHeaderValue(name)) - : new GitHubClient(new ProductHeaderValue(name), new InMemoryCredentialStore(new Credentials(token))); - + var client = GetGitHubClient(name, token); var repo = await client.Repository.Get(name, repositoryName); return await client.Repository.Commit.Get(repo.Id, sha); } + + private HttpClient CreateHttpClient(string token, string userAgent) + { + var httpClient = _clientFactory.CreateClient(HttpClientName); + if (!token.IsNullOrWhiteSpace()) + { + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Token", token); + } + + if (!userAgent.IsNullOrWhiteSpace()) + { + httpClient.DefaultRequestHeaders.Add("User-Agent", userAgent); + } + + return httpClient; + } + + private static GitHubClient GetGitHubClient(string name, string token) + { + return token.IsNullOrWhiteSpace() + ? new GitHubClient(new ProductHeaderValue(name)) + : new GitHubClient(new ProductHeaderValue(name), new InMemoryCredentialStore(new Credentials(token))); + } } } diff --git a/modules/docs/src/Volo.Docs.Domain/Volo/Extensions/NewtonsoftJsonExtensions.cs b/modules/docs/src/Volo.Docs.Domain/Volo/Extensions/NewtonsoftJsonExtensions.cs new file mode 100644 index 0000000000..200baa9c2c --- /dev/null +++ b/modules/docs/src/Volo.Docs.Domain/Volo/Extensions/NewtonsoftJsonExtensions.cs @@ -0,0 +1,22 @@ +using System; +using Newtonsoft.Json; + +namespace Volo.Extensions +{ + public static class JsonConvertExtensions + { + public static bool TryDeserializeObject(string jsonContent, out T result) + { + try + { + result = JsonConvert.DeserializeObject(jsonContent); + return true; + } + catch + { + result = default; + return false; + } + } + } +} \ No newline at end of file diff --git a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/EntityFrameworkCore/DocsDbContextModelBuilderExtensions.cs b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/EntityFrameworkCore/DocsDbContextModelBuilderExtensions.cs index 64a85db140..379ca8792d 100644 --- a/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/EntityFrameworkCore/DocsDbContextModelBuilderExtensions.cs +++ b/modules/docs/src/Volo.Docs.EntityFrameworkCore/Volo/Docs/EntityFrameworkCore/DocsDbContextModelBuilderExtensions.cs @@ -63,6 +63,8 @@ namespace Volo.Docs.EntityFrameworkCore { b.ToTable(options.TablePrefix + "DocumentContributors", options.Schema); + b.ConfigureByConvention(); + b.HasKey(x => new { x.DocumentId, x.Username }); }); } diff --git a/modules/docs/src/Volo.Docs.Web/HtmlConverting/ScribanDocumentSectionRenderer.cs b/modules/docs/src/Volo.Docs.Web/HtmlConverting/ScribanDocumentSectionRenderer.cs index 4a4f3b2142..990f2a0dec 100644 --- a/modules/docs/src/Volo.Docs.Web/HtmlConverting/ScribanDocumentSectionRenderer.cs +++ b/modules/docs/src/Volo.Docs.Web/HtmlConverting/ScribanDocumentSectionRenderer.cs @@ -7,16 +7,17 @@ using Newtonsoft.Json; using Scriban; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Volo.Docs.Documents; +using Volo.Abp; +using Volo.Extensions; namespace Volo.Docs.HtmlConverting { public class ScribanDocumentSectionRenderer : IDocumentSectionRenderer { - private const string jsonOpener = "````json"; - private const string jsonCloser = "````"; - private const string docs_param = "//[doc-params]"; - private const string docs_templates = "//[doc-template]"; + private const string JsonOpener = "````json"; + private const string JsonCloser = "````"; + private const string DocsParam = "//[doc-params]"; + private const string DocsTemplates = "//[doc-template]"; public ILogger Logger { get; set; } @@ -40,6 +41,7 @@ namespace Volo.Docs.HtmlConverting } var result = await scribanTemplate.RenderAsync(parameters); + return RemoveOptionsJson(result); } @@ -47,7 +49,7 @@ namespace Volo.Docs.HtmlConverting { try { - if (!document.Contains(jsonOpener) || !document.Contains(docs_param)) + if (!document.Contains(JsonOpener) || !document.Contains(DocsParam)) { return new Dictionary>(); } @@ -59,9 +61,14 @@ namespace Volo.Docs.HtmlConverting return new Dictionary>(); } - var pureJson = insideJsonSection.Replace(docs_param, "").Trim(); + var pureJson = insideJsonSection.Replace(DocsParam, "").Trim(); + + if (!JsonConvertExtensions.TryDeserializeObject>>(pureJson, out var availableParameters)) + { + throw new UserFriendlyException("ERROR-20200327: Cannot validate JSON content for `AvailableParameters`!"); + } - return JsonConvert.DeserializeObject>>(pureJson); + return await Task.FromResult(availableParameters); } catch (Exception) { @@ -70,12 +77,13 @@ namespace Volo.Docs.HtmlConverting } } - private string RemoveOptionsJson(string document) + private static string RemoveOptionsJson(string document) { var orgDocument = document; + try { - if (!document.Contains(jsonOpener) || !document.Contains(docs_param)) + if (!document.Contains(JsonOpener) || !document.Contains(DocsParam)) { return orgDocument; } @@ -88,8 +96,9 @@ namespace Volo.Docs.HtmlConverting } return document.Remove( - jsonBeginningIndex - jsonOpener.Length, (jsonEndingIndex + jsonCloser.Length) - (jsonBeginningIndex - jsonOpener.Length) - ); + jsonBeginningIndex - JsonOpener.Length, + (jsonEndingIndex + JsonCloser.Length) - (jsonBeginningIndex - JsonOpener.Length) + ); } catch (Exception) { @@ -97,25 +106,25 @@ namespace Volo.Docs.HtmlConverting } } - private (int, int, string) GetJsonBeginEndIndexesAndPureJson(string document) + private static (int, int, string) GetJsonBeginEndIndexesAndPureJson(string document) { var searchedIndex = 0; while (searchedIndex < document.Length) { - var jsonBeginningIndex = document.Substring(searchedIndex).IndexOf(jsonOpener, StringComparison.Ordinal) + jsonOpener.Length + searchedIndex; + var jsonBeginningIndex = document.Substring(searchedIndex).IndexOf(JsonOpener, StringComparison.Ordinal) + JsonOpener.Length + searchedIndex; if (jsonBeginningIndex < 0) { return (-1, -1, ""); } - var jsonEndingIndex = document.Substring(jsonBeginningIndex).IndexOf(jsonCloser, StringComparison.Ordinal) + jsonBeginningIndex; + var jsonEndingIndex = document.Substring(jsonBeginningIndex).IndexOf(JsonCloser, StringComparison.Ordinal) + jsonBeginningIndex; var insideJsonSection = document[jsonBeginningIndex..jsonEndingIndex]; - if (insideJsonSection.IndexOf(docs_param) < 0) + if (insideJsonSection.IndexOf(DocsParam, StringComparison.Ordinal) < 0) { - searchedIndex = jsonEndingIndex + jsonCloser.Length; + searchedIndex = jsonEndingIndex + JsonCloser.Length; continue; } @@ -129,68 +138,84 @@ namespace Volo.Docs.HtmlConverting { var templates = new List(); - while (documentContent.Contains(jsonOpener)) + while (documentContent.Contains(JsonOpener)) { var afterJsonOpener = documentContent.Substring( - documentContent.IndexOf(jsonOpener, StringComparison.Ordinal) + jsonOpener.Length); + documentContent.IndexOf(JsonOpener, StringComparison.Ordinal) + JsonOpener.Length + ); var betweenJsonOpenerAndCloser = afterJsonOpener.Substring(0, - afterJsonOpener.IndexOf(jsonCloser, StringComparison.Ordinal)); + afterJsonOpener.IndexOf(JsonCloser, StringComparison.Ordinal) + ); documentContent = afterJsonOpener.Substring( - afterJsonOpener.IndexOf(jsonCloser, StringComparison.Ordinal) + jsonCloser.Length); + afterJsonOpener.IndexOf(JsonCloser, StringComparison.Ordinal) + JsonCloser.Length + ); - if (!betweenJsonOpenerAndCloser.Contains(docs_templates)) + if (!betweenJsonOpenerAndCloser.Contains(DocsTemplates)) { continue; } - var json = betweenJsonOpenerAndCloser.Substring(betweenJsonOpenerAndCloser.IndexOf(docs_templates, StringComparison.Ordinal) + docs_templates.Length); + var json = betweenJsonOpenerAndCloser.Substring(betweenJsonOpenerAndCloser.IndexOf(DocsTemplates, StringComparison.Ordinal) + DocsTemplates.Length); - var template = JsonConvert.DeserializeObject(json); + if (!JsonConvertExtensions.TryDeserializeObject(json, out var template)) + { + throw new UserFriendlyException($"ERROR-20200327: Cannot validate JSON content for `AvailableParameters`!"); + } templates.Add(template); } - return templates; + return await Task.FromResult(templates); } - private string SetPartialTemplates(string document, List templates) + private static string SetPartialTemplates(string document, IReadOnlyCollection templates) { var newDocument = new StringBuilder(); - while (document.Contains(jsonOpener)) + while (document.Contains(JsonOpener)) { var beforeJson = document.Substring(0, - document.IndexOf(jsonOpener, StringComparison.Ordinal) + jsonOpener.Length); + document.IndexOf(JsonOpener, StringComparison.Ordinal) + JsonOpener.Length + ); var afterJsonOpener = document.Substring( - document.IndexOf(jsonOpener, StringComparison.Ordinal) + jsonOpener.Length); + document.IndexOf(JsonOpener, StringComparison.Ordinal) + JsonOpener.Length + ); var betweenJsonOpenerAndCloser = afterJsonOpener.Substring(0, - afterJsonOpener.IndexOf(jsonCloser, StringComparison.Ordinal)); + afterJsonOpener.IndexOf(JsonCloser, StringComparison.Ordinal) + ); - if (!betweenJsonOpenerAndCloser.Contains(docs_templates)) + if (!betweenJsonOpenerAndCloser.Contains(DocsTemplates)) { document = afterJsonOpener.Substring( - afterJsonOpener.IndexOf(jsonCloser, StringComparison.Ordinal) + jsonCloser.Length); - newDocument.Append(beforeJson + betweenJsonOpenerAndCloser + jsonCloser); + afterJsonOpener.IndexOf(JsonCloser, StringComparison.Ordinal) + JsonCloser.Length + ); + + newDocument.Append(beforeJson + betweenJsonOpenerAndCloser + JsonCloser); continue; } - var json = betweenJsonOpenerAndCloser.Substring(betweenJsonOpenerAndCloser.IndexOf(docs_templates, StringComparison.Ordinal) + docs_templates.Length); - - var templatePath = JsonConvert.DeserializeObject(json)?.Path; + var json = betweenJsonOpenerAndCloser.Substring( + betweenJsonOpenerAndCloser.IndexOf(DocsTemplates, StringComparison.Ordinal) + DocsTemplates.Length + ); - var template = templates.FirstOrDefault(t => t.Path == templatePath); + if (JsonConvertExtensions.TryDeserializeObject(json, out var documentPartialTemplateWithValuesDto)) + { + var template = templates.FirstOrDefault(t => t.Path == documentPartialTemplateWithValuesDto.Path); - var beforeTemplate = document.Substring(0, - document.IndexOf(jsonOpener, StringComparison.Ordinal)); + var beforeTemplate = document.Substring(0, + document.IndexOf(JsonOpener, StringComparison.Ordinal) + ); - newDocument.Append(beforeTemplate + template?.Content + jsonCloser); + newDocument.Append(beforeTemplate + template?.Content + JsonCloser); - document = afterJsonOpener.Substring( - afterJsonOpener.IndexOf(jsonCloser, StringComparison.Ordinal) + jsonCloser.Length); + document = afterJsonOpener.Substring( + afterJsonOpener.IndexOf(JsonCloser, StringComparison.Ordinal) + JsonCloser.Length + ); + } } newDocument.Append(document); diff --git a/modules/docs/src/Volo.Docs.Web/Markdown/MarkdownDocumentToHtmlConverter.cs b/modules/docs/src/Volo.Docs.Web/Markdown/MarkdownDocumentToHtmlConverter.cs index 5d7282a7e6..12dec9b304 100644 --- a/modules/docs/src/Volo.Docs.Web/Markdown/MarkdownDocumentToHtmlConverter.cs +++ b/modules/docs/src/Volo.Docs.Web/Markdown/MarkdownDocumentToHtmlConverter.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Text.RegularExpressions; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; @@ -24,9 +25,9 @@ namespace Volo.Docs.Markdown } private const string MdLinkFormat = "[{0}]({1}{2}/{3}/{4}{5}/{6})"; - private const string MarkdownLinkRegExp = @"\[(.*?)\]\((.*?)\)"; + private const string MarkdownLinkRegExp = @"\[(.*?)\]\(((.*?)(\?(.*?))*?)\)"; private const string AnchorLinkRegExp = @"]+href=\""(.*?)\""[^>]*>(.*)?"; - + public virtual string Convert(ProjectDto project, DocumentWithDetailsDto document, string version, string languageCode) { @@ -45,7 +46,7 @@ namespace Volo.Docs.Markdown return _markdownConverter.ConvertToHtml(content); } - + protected virtual string NormalizeLinks( string content, string projectShortName, @@ -53,10 +54,29 @@ namespace Volo.Docs.Markdown string documentLocalDirectory, string languageCode) { - var normalized = Regex.Replace(content, MarkdownLinkRegExp, delegate (Match match) + + var normalized = NormalizeMdLinks(content, projectShortName, version, documentLocalDirectory, languageCode); + + normalized = NormalizeAnchorLinks(projectShortName, version, documentLocalDirectory, languageCode, normalized); + + return normalized; + } + + private string NormalizeMdLinks(string content, + string projectShortName, + string version, + string documentLocalDirectory, + string languageCode) + { + return Regex.Replace(content, MarkdownLinkRegExp, delegate (Match match) { - var link = match.Groups[2].Value; - if (UrlHelper.IsExternalLink(link) || !link.EndsWith(".md")) + var link = match.Groups[3].Value; + if (UrlHelper.IsExternalLink(link)) + { + return match.Value; + } + + if (!link.EndsWith(".md")) { return match.Value; } @@ -64,6 +84,13 @@ namespace Volo.Docs.Markdown var displayText = match.Groups[1].Value; var documentName = RemoveFileExtension(link); + + var hasUrlParameter = match.Groups.Count > 3 && !match.Groups[4].Value.IsNullOrEmpty(); + if (hasUrlParameter) + { + documentName += match.Groups[4].Value; + } + var documentLocalDirectoryNormalized = documentLocalDirectory.TrimStart('/').TrimEnd('/'); if (!string.IsNullOrWhiteSpace(documentLocalDirectoryNormalized)) { @@ -81,8 +108,12 @@ namespace Volo.Docs.Markdown documentName ); }); + } - normalized = Regex.Replace(normalized, AnchorLinkRegExp, delegate (Match match) + private string NormalizeAnchorLinks(string projectShortName, string version, string documentLocalDirectory, + string languageCode, string normalized) + { + return Regex.Replace(normalized, AnchorLinkRegExp, delegate (Match match) { var link = match.Groups[1].Value; if (UrlHelper.IsExternalLink(link)) @@ -109,8 +140,6 @@ namespace Volo.Docs.Markdown documentName ); }); - - return normalized; } private static string RemoveFileExtension(string documentName) diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index 67e5352e22..6fcf402d70 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -68,7 +68,7 @@ }
-