diff --git a/.editorconfig b/.editorconfig
index 17c130c..87da84e 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -2,3 +2,80 @@
# CS1591: Fehledes XML-Kommentar für öffentlich sichtbaren Typ oder Element
dotnet_diagnostic.CS1591.severity = none
+csharp_indent_labels = one_less_than_current
+csharp_using_directive_placement = outside_namespace:silent
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_prefer_braces = true:silent
+csharp_style_namespace_declarations = block_scoped:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_space_around_binary_operators = before_and_after
+
+[*.{cs,vb}]
+#### Naming styles ####
+
+# Naming rules
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# Symbol specifications
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers =
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers =
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers =
+
+# Naming styles
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix =
+dotnet_naming_style.begins_with_i.word_separator =
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.pascal_case.required_prefix =
+dotnet_naming_style.pascal_case.required_suffix =
+dotnet_naming_style.pascal_case.word_separator =
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+tab_width = 4
+indent_size = 4
+end_of_line = crlf
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_auto_properties = true:silent
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
diff --git a/.github/workflows/apigenerator.yml b/.github/workflows/apigenerator.yml
new file mode 100644
index 0000000..d86858a
--- /dev/null
+++ b/.github/workflows/apigenerator.yml
@@ -0,0 +1,74 @@
+name: Build and deploy .NET Core application to windows webapp apigenerator with API Management Service ApiGeneratorSampleApIapi
+on:
+ push:
+ branches:
+ - vnext
+env:
+ AZURE_WEBAPP_NAME: apigenerator
+ DOTNET_CORE_VERSION: 6.0.x
+ WORKING_DIRECTORY: sample\ApiGeneratorSampleApp
+ CONFIGURATION: Release
+ AZURE_WEBAPP_PACKAGE_PATH: sample\ApiGeneratorSampleApp/publish
+ AZURE_APIM_RESOURCE_PATH: /generated
+ AZURE_APIM_RESOURCEGROUP: apigenerator_group
+ AZURE_APIM_SERVICENAME: ApiGeneratorSampleApIapi
+ AZURE_APIM_API_ID: ApiGeneratorSampleApI
+ AZURE_APIM_APPSERVICEURL: https://apigenerator.azurewebsites.net:80/
+ SWASHBUCLE_ASPNET_CORE_CLI_PACKAGE_VERSION: 5.6.3
+ SWASHBUCKLE_DOTNET_CORE_VERSION: 3.1.x
+ API_IMPORT_SPECIFICATION_PATH: sample\ApiGeneratorSampleApp/publish/swagger.json
+ API_IMPORT_DLL: sample\ApiGeneratorSampleApp/publish/ApiGeneratorSampleApI.dll
+ API_IMPORT_VERSION: v1
+jobs:
+ build:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: ${{ env.DOTNET_CORE_VERSION }}
+ - name: Setup SwashBuckle .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: ${{ env.SWASHBUCKLE_DOTNET_CORE_VERSION }}
+ - name: Restore
+ run: dotnet restore ${{ env.WORKING_DIRECTORY }}
+ - name: Build
+ run: dotnet build ${{ env.WORKING_DIRECTORY }} --configuration ${{ env.CONFIGURATION }} --no-restore
+ - name: Test
+ run: dotnet test ${{ env.WORKING_DIRECTORY }} --no-build
+ - name: Publish
+ run: dotnet publish ${{ env.WORKING_DIRECTORY }} --configuration ${{ env.CONFIGURATION }} --no-build --output ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
+ - name: Install Swashbuckle CLI .NET Global Tool
+ run: dotnet tool install --global Swashbuckle.AspNetCore.Cli --version ${{ env.SWASHBUCLE_ASPNET_CORE_CLI_PACKAGE_VERSION }}
+ working-directory: ${{ env.WORKING_DIRECTORY }}
+ - name: Publish Artifacts
+ uses: actions/upload-artifact@v1.0.0
+ with:
+ name: webapp
+ path: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
+ deploy:
+ runs-on: windows-latest
+ needs: build
+ steps:
+ - name: Download artifact from build job
+ uses: actions/download-artifact@v2
+ with:
+ name: webapp
+ path: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
+ - name: Deploy to Azure WebApp
+ uses: azure/webapps-deploy@v2
+ with:
+ app-name: ${{ env.AZURE_WEBAPP_NAME }}
+ package: ${{ env.AZURE_WEBAPP_PACKAGE_PATH }}
+ publish-profile: ${{ secrets.apigenerator_c1d7 }}
+ - name: Azure Login
+ uses: azure/login@v1
+ with:
+ creds: ${{ secrets.ApiGeneratorSampleApIapi_spn }}
+ - name: Import API into Azure API Management
+ run: az apim api import --path "${{ env.AZURE_APIM_RESOURCE_PATH }}" --resource-group "${{ env.AZURE_APIM_RESOURCEGROUP }}" --service-name "${{ env.AZURE_APIM_SERVICENAME }}" --api-id "${{ env.AZURE_APIM_API_ID }}" --service-url "${{ env.AZURE_APIM_APPSERVICEURL }}" --specification-path "${{ env.API_IMPORT_SPECIFICATION_PATH }}" --specification-format OpenApi --subscription-required false
+ - name: logout
+ run: >
+ az logout
diff --git a/TCDev.APIGenerator.sln b/TCDev.APIGenerator.sln
index b748b30..df836d5 100644
--- a/TCDev.APIGenerator.sln
+++ b/TCDev.APIGenerator.sln
@@ -14,8 +14,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TCDev.APIGenerator.Caching", "src\TCDev.APIGenerator.Caching\TCDev.APIGenerator.Caching.csproj", "{0C8E23AD-AC5D-41D4-9F67-0ECF3D1C4BE1}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TCDev.APIGenerator.GraphQL", "src\TCDev.APIGenerator.GraphQL\TCDev.APIGenerator.GraphQL.csproj", "{EDEA4DF4-49DF-4205-9B8E-61D76F26BA8D}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TCDev.APIGenerator.Schema", "src\TCDev.APIGenerator.Schema\TCDev.APIGenerator.Schema.csproj", "{94E59385-D259-40A1-A373-1FBD0A42CD63}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiGenerator", "ApiGenerator", "{4189D7E0-F171-4267-AC64-C9A83BB1B559}"
@@ -24,11 +22,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sample App", "Sample App",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleAppNuget", "sample\SampleAppNuget\SampleAppNuget.csproj", "{BA9E04E6-4B66-4369-9B2F-C6CEC9499851}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TCDev.APIGenerator.Json", "src\TCDev.APIGenerator.DbFirst\TCDev.APIGenerator.Json.csproj", "{7F3574D1-7421-4824-A0BB-522F3BC9BAC4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleAppJson", "sample\SampleAppJson\SampleAppJson.csproj", "{25AE6B2A-822D-411B-AB65-068E9E0E41D5}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
DebugWithSampleApp|Any CPU = DebugWithSampleApp|Any CPU
Release|Any CPU = Release|Any CPU
+ SampleAppJson|Any CPU = SampleAppJson|Any CPU
SampleAppNuget|Any CPU = SampleAppNuget|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
@@ -38,33 +41,56 @@ Global
{FE869C02-6C9A-4D9B-BBE2-56F1B21B2A55}.DebugWithSampleApp|Any CPU.Build.0 = DebugWithSampleApp|Any CPU
{FE869C02-6C9A-4D9B-BBE2-56F1B21B2A55}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FE869C02-6C9A-4D9B-BBE2-56F1B21B2A55}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FE869C02-6C9A-4D9B-BBE2-56F1B21B2A55}.SampleAppJson|Any CPU.ActiveCfg = SampleAppJson|Any CPU
+ {FE869C02-6C9A-4D9B-BBE2-56F1B21B2A55}.SampleAppJson|Any CPU.Build.0 = SampleAppJson|Any CPU
{FE869C02-6C9A-4D9B-BBE2-56F1B21B2A55}.SampleAppNuget|Any CPU.ActiveCfg = SampleAppNuget|Any CPU
{303BF897-594C-4911-91CF-3887A8B8E839}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {303BF897-594C-4911-91CF-3887A8B8E839}.Debug|Any CPU.Build.0 = Debug|Any CPU
{303BF897-594C-4911-91CF-3887A8B8E839}.DebugWithSampleApp|Any CPU.ActiveCfg = DebugWithSampleApp|Any CPU
{303BF897-594C-4911-91CF-3887A8B8E839}.DebugWithSampleApp|Any CPU.Build.0 = DebugWithSampleApp|Any CPU
{303BF897-594C-4911-91CF-3887A8B8E839}.Release|Any CPU.ActiveCfg = Release|Any CPU
{303BF897-594C-4911-91CF-3887A8B8E839}.Release|Any CPU.Build.0 = Release|Any CPU
+ {303BF897-594C-4911-91CF-3887A8B8E839}.SampleAppJson|Any CPU.ActiveCfg = SampleAppJson|Any CPU
{303BF897-594C-4911-91CF-3887A8B8E839}.SampleAppNuget|Any CPU.ActiveCfg = SampleAppNuget|Any CPU
{0C8E23AD-AC5D-41D4-9F67-0ECF3D1C4BE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C8E23AD-AC5D-41D4-9F67-0ECF3D1C4BE1}.DebugWithSampleApp|Any CPU.ActiveCfg = DebugWithSampleApp|Any CPU
{0C8E23AD-AC5D-41D4-9F67-0ECF3D1C4BE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {0C8E23AD-AC5D-41D4-9F67-0ECF3D1C4BE1}.SampleAppJson|Any CPU.ActiveCfg = SampleAppJson|Any CPU
{0C8E23AD-AC5D-41D4-9F67-0ECF3D1C4BE1}.SampleAppNuget|Any CPU.ActiveCfg = SampleAppNuget|Any CPU
- {EDEA4DF4-49DF-4205-9B8E-61D76F26BA8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {EDEA4DF4-49DF-4205-9B8E-61D76F26BA8D}.DebugWithSampleApp|Any CPU.ActiveCfg = DebugWithSampleApp|Any CPU
- {EDEA4DF4-49DF-4205-9B8E-61D76F26BA8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {EDEA4DF4-49DF-4205-9B8E-61D76F26BA8D}.SampleAppNuget|Any CPU.ActiveCfg = SampleAppNuget|Any CPU
{94E59385-D259-40A1-A373-1FBD0A42CD63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94E59385-D259-40A1-A373-1FBD0A42CD63}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94E59385-D259-40A1-A373-1FBD0A42CD63}.DebugWithSampleApp|Any CPU.ActiveCfg = DebugWithSampleApp|Any CPU
{94E59385-D259-40A1-A373-1FBD0A42CD63}.DebugWithSampleApp|Any CPU.Build.0 = DebugWithSampleApp|Any CPU
{94E59385-D259-40A1-A373-1FBD0A42CD63}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94E59385-D259-40A1-A373-1FBD0A42CD63}.Release|Any CPU.Build.0 = Release|Any CPU
+ {94E59385-D259-40A1-A373-1FBD0A42CD63}.SampleAppJson|Any CPU.ActiveCfg = SampleAppJson|Any CPU
+ {94E59385-D259-40A1-A373-1FBD0A42CD63}.SampleAppJson|Any CPU.Build.0 = SampleAppJson|Any CPU
{94E59385-D259-40A1-A373-1FBD0A42CD63}.SampleAppNuget|Any CPU.ActiveCfg = SampleAppNuget|Any CPU
{BA9E04E6-4B66-4369-9B2F-C6CEC9499851}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BA9E04E6-4B66-4369-9B2F-C6CEC9499851}.DebugWithSampleApp|Any CPU.ActiveCfg = DebugWithSampleApp|Any CPU
{BA9E04E6-4B66-4369-9B2F-C6CEC9499851}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {BA9E04E6-4B66-4369-9B2F-C6CEC9499851}.SampleAppJson|Any CPU.ActiveCfg = SampleAppJson|Any CPU
{BA9E04E6-4B66-4369-9B2F-C6CEC9499851}.SampleAppNuget|Any CPU.ActiveCfg = SampleAppNuget|Any CPU
{BA9E04E6-4B66-4369-9B2F-C6CEC9499851}.SampleAppNuget|Any CPU.Build.0 = SampleAppNuget|Any CPU
+ {7F3574D1-7421-4824-A0BB-522F3BC9BAC4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7F3574D1-7421-4824-A0BB-522F3BC9BAC4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7F3574D1-7421-4824-A0BB-522F3BC9BAC4}.DebugWithSampleApp|Any CPU.ActiveCfg = DebugWithSampleApp|Any CPU
+ {7F3574D1-7421-4824-A0BB-522F3BC9BAC4}.DebugWithSampleApp|Any CPU.Build.0 = DebugWithSampleApp|Any CPU
+ {7F3574D1-7421-4824-A0BB-522F3BC9BAC4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7F3574D1-7421-4824-A0BB-522F3BC9BAC4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7F3574D1-7421-4824-A0BB-522F3BC9BAC4}.SampleAppJson|Any CPU.ActiveCfg = SampleAppJson|Any CPU
+ {7F3574D1-7421-4824-A0BB-522F3BC9BAC4}.SampleAppNuget|Any CPU.ActiveCfg = SampleAppNuget|Any CPU
+ {7F3574D1-7421-4824-A0BB-522F3BC9BAC4}.SampleAppNuget|Any CPU.Build.0 = SampleAppNuget|Any CPU
+ {25AE6B2A-822D-411B-AB65-068E9E0E41D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {25AE6B2A-822D-411B-AB65-068E9E0E41D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {25AE6B2A-822D-411B-AB65-068E9E0E41D5}.DebugWithSampleApp|Any CPU.ActiveCfg = DebugWithSampleApp|Any CPU
+ {25AE6B2A-822D-411B-AB65-068E9E0E41D5}.DebugWithSampleApp|Any CPU.Build.0 = DebugWithSampleApp|Any CPU
+ {25AE6B2A-822D-411B-AB65-068E9E0E41D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {25AE6B2A-822D-411B-AB65-068E9E0E41D5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {25AE6B2A-822D-411B-AB65-068E9E0E41D5}.SampleAppJson|Any CPU.ActiveCfg = SampleAppJson|Any CPU
+ {25AE6B2A-822D-411B-AB65-068E9E0E41D5}.SampleAppJson|Any CPU.Build.0 = SampleAppJson|Any CPU
+ {25AE6B2A-822D-411B-AB65-068E9E0E41D5}.SampleAppNuget|Any CPU.ActiveCfg = SampleAppNuget|Any CPU
+ {25AE6B2A-822D-411B-AB65-068E9E0E41D5}.SampleAppNuget|Any CPU.Build.0 = SampleAppNuget|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -73,9 +99,10 @@ Global
{FE869C02-6C9A-4D9B-BBE2-56F1B21B2A55} = {4189D7E0-F171-4267-AC64-C9A83BB1B559}
{303BF897-594C-4911-91CF-3887A8B8E839} = {8CC9B68F-E1C2-45B3-8814-B9FF4E1B2AB8}
{0C8E23AD-AC5D-41D4-9F67-0ECF3D1C4BE1} = {4189D7E0-F171-4267-AC64-C9A83BB1B559}
- {EDEA4DF4-49DF-4205-9B8E-61D76F26BA8D} = {4189D7E0-F171-4267-AC64-C9A83BB1B559}
{94E59385-D259-40A1-A373-1FBD0A42CD63} = {4189D7E0-F171-4267-AC64-C9A83BB1B559}
{BA9E04E6-4B66-4369-9B2F-C6CEC9499851} = {8CC9B68F-E1C2-45B3-8814-B9FF4E1B2AB8}
+ {7F3574D1-7421-4824-A0BB-522F3BC9BAC4} = {4189D7E0-F171-4267-AC64-C9A83BB1B559}
+ {25AE6B2A-822D-411B-AB65-068E9E0E41D5} = {8CC9B68F-E1C2-45B3-8814-B9FF4E1B2AB8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {315BF454-8B91-42C5-A113-B59C72AE69C8}
diff --git a/sample/ApiGeneratorSampleApp/ApiDefinition.json b/sample/ApiGeneratorSampleApp/ApiDefinition.json
new file mode 100644
index 0000000..4ffc03e
--- /dev/null
+++ b/sample/ApiGeneratorSampleApp/ApiDefinition.json
@@ -0,0 +1,45 @@
+[
+
+ {
+ "name": "MakeJSON",
+ "route": "/MakeJSON",
+ "idType": "int",
+ "authorize": true,
+ "scopesRead": [ "all.read" ],
+ "scopesWrite": [ "all.write" ],
+ "Fields": [
+ {
+ "name": "Name",
+ "type": "string"
+ },
+ {
+ "name": "Description",
+ "Type": "string"
+
+ }
+ ]
+ },
+ {
+ "name": "CarJSON",
+ "route": "/CarJSON",
+ "idType": "int",
+ "Fields": [
+ {
+ "name": "Name",
+ "type": "string"
+ },
+ {
+ "name": "Description",
+ "Type": "string"
+ },
+ {
+ "name": "Make",
+ "type": "virtual MakeJSON"
+ },
+ {
+ "name": "MakeId",
+ "type": "int"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/sample/ApiGeneratorSampleApp/ApiGeneratorSampleApI.csproj b/sample/ApiGeneratorSampleApp/ApiGeneratorSampleApI.csproj
index d25d36b..ad64568 100644
--- a/sample/ApiGeneratorSampleApp/ApiGeneratorSampleApI.csproj
+++ b/sample/ApiGeneratorSampleApp/ApiGeneratorSampleApI.csproj
@@ -3,7 +3,7 @@
net6.0
aspnet-ApiGeneratorSampleApp-56AA10DB-26A2-414F-AFD8-1D3546BD678D
- Debug;Release;DebugWithSampleApp;SampleAppNuget
+ Debug;Release;DebugWithSampleApp;SampleAppNuget;SampleAppJson
@@ -14,24 +14,36 @@
ApiGeneratorSampleApI.xml
+
+ ApiGeneratorSampleApI.xml
+
+
-
-
-
-
+
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
+
+
+
+
+
+
-
+
+ Always
+
diff --git a/sample/ApiGeneratorSampleApp/ApiGeneratorSampleApI.xml b/sample/ApiGeneratorSampleApp/ApiGeneratorSampleApI.xml
index 2d38add..b8dc9c7 100644
--- a/sample/ApiGeneratorSampleApp/ApiGeneratorSampleApI.xml
+++ b/sample/ApiGeneratorSampleApp/ApiGeneratorSampleApI.xml
@@ -6,23 +6,8 @@
- This is the minimal sample, yes this is a working api ;)
+ This is the minimal sample, yes this is a working api ;)
-
-
- Before Delete Hook
-
-
-
-
-
-
- Before Update Hook
-
-
-
-
-
diff --git a/sample/ApiGeneratorSampleApp/ApiGeneratorSampleApp.xml b/sample/ApiGeneratorSampleApp/ApiGeneratorSampleApp.xml
index 2d38add..023cd23 100644
--- a/sample/ApiGeneratorSampleApp/ApiGeneratorSampleApp.xml
+++ b/sample/ApiGeneratorSampleApp/ApiGeneratorSampleApp.xml
@@ -6,7 +6,7 @@
- This is the minimal sample, yes this is a working api ;)
+ This is the minimal sample, yes this is a working api ;)
diff --git a/sample/ApiGeneratorSampleApp/Model/Car.cs b/sample/ApiGeneratorSampleApp/Model/Car.cs
new file mode 100644
index 0000000..4cb3091
--- /dev/null
+++ b/sample/ApiGeneratorSampleApp/Model/Car.cs
@@ -0,0 +1,70 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using TCDev.ApiGenerator.Attributes;
+using TCDev.ApiGenerator.Interfaces;
+
+namespace ApiGeneratorSampleApI.Model
+{
+
+ [Api("/car",
+ authorize: true,
+ requiredReadScopes: new string[] { "car.read" },
+ requiredWriteScopes: new string[] { "car.write" })]
+ public class Car : IObjectBase
+ {
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ [SwaggerIgnore]
+ public Guid Id { get; set; } = Guid.NewGuid();
+
+
+ [EmailAddress]
+ public string Name { get; set; }
+
+ public string Description { get; set; }
+
+ public string Color { get; set; }
+
+ public Make? Make { get; set; }
+
+ public Model? Model { get; set; }
+ }
+
+
+ [Api("/carMakes",
+ authorize: true,
+ requiredReadScopes: new string[] { "make.read" },
+ requiredWriteScopes: new string[] { "make.write" })]
+ public class Make : IObjectBase
+ {
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ [SwaggerIgnore]
+ public Guid Id { get; set; } = Guid.NewGuid();
+ public string Name { get; set; }
+
+ public string Description { get; set; }
+
+
+ public Model? Model { get; set; }
+ }
+
+
+
+ [Api("/carModel",
+ authorize: true,
+ requiredReadScopes: new string[] { "model.read" },
+ requiredWriteScopes: new string[] { "model.write" })]
+ public class Model : IObjectBase
+ {
+ [Key]
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ [SwaggerIgnore]
+ public Guid Id { get; set; } = Guid.NewGuid();
+ public string Name { get; set; }
+
+ public string Description { get; set; }
+ }
+
+}
diff --git a/sample/ApiGeneratorSampleApp/Model/MinimalSample.cs b/sample/ApiGeneratorSampleApp/Model/MinimalSample.cs
index 6526985..1e96359 100644
--- a/sample/ApiGeneratorSampleApp/Model/MinimalSample.cs
+++ b/sample/ApiGeneratorSampleApp/Model/MinimalSample.cs
@@ -1,17 +1,20 @@
-using TCDev.ApiGenerator.Attributes;
+// TCDev.de 2022/03/24
+// ApiGeneratorSampleApI.MinimalSample.cs
+// https://github.com/DeeJayTC/net-dynamic-api
+
+using TCDev.ApiGenerator.Attributes;
using TCDev.ApiGenerator.Interfaces;
namespace ApiGeneratorSampleApI.Model
{
-
///
- /// This is the minimal sample, yes this is a working api ;)
+ /// This is the minimal sample, yes this is a working api ;)
///
- [Api("/minimal")]
- public class MinimalSample : IObjectBase
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public int Value { get; set; }
- }
+ [Api("/minimal")]
+ public class MinimalSample : IObjectBase
+ {
+ public string Name { get; set; }
+ public int Value { get; set; }
+ public int Id { get; set; }
+ }
}
diff --git a/sample/ApiGeneratorSampleApp/Model/Person.cs b/sample/ApiGeneratorSampleApp/Model/Person.cs
index 06d456c..df0ecc6 100644
--- a/sample/ApiGeneratorSampleApp/Model/Person.cs
+++ b/sample/ApiGeneratorSampleApp/Model/Person.cs
@@ -2,60 +2,19 @@
// Apache 2.0 License
// https://www.github.com/deejaytc/dotnet-utils
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
-using System.Collections.Generic;
-using System.Threading.Tasks;
using TCDev.ApiGenerator.Attributes;
using TCDev.ApiGenerator.Interfaces;
-using TCDev.ApiGenerator.Schemes.Interfaces;
-using TCDev.APIGenerator.Schema.Interfaces;
namespace ApiGeneratorSampleApI.Model
{
- [Api("/people", ApiMethodsToGenerate.All )]
- public class Person : Trackable,
- IObjectBase,
- IBeforeUpdate, // Before Update Hook
- IBeforeDelete, // BeforeDelete Hook
- IEntityTypeConfiguration // Configure Table Options yourself
- {
+ [Api("/people")]
+ public class Person : IObjectBase
+ {
public string Name { get; set; }
public DateTime Date { get; set; }
public string Description { get; set; }
public int Age { get; set; }
public Guid Id { get; set; }
-
- ///
- /// Before Delete Hook
- ///
- ///
- ///
- public Task BeforeDelete(Person item)
- {
- // NOOOO Don't delete me!
- return Task.FromResult(true);
- }
-
- ///
- /// Before Update Hook
- ///
- ///
- ///
- ///
- public Task BeforeUpdate(Person newPerson, Person oldPerson)
- {
- newPerson.Age = 333;
-
- return Task.FromResult(newPerson);
- }
-
- public void Configure(EntityTypeBuilder builder)
- {
- builder.ToTable("MyFancyTableName");
- //....all the other EF Core Options
- }
- }
-
+ }
}
\ No newline at end of file
diff --git a/sample/ApiGeneratorSampleApp/Program.cs b/sample/ApiGeneratorSampleApp/Program.cs
index e32e537..fec1391 100644
--- a/sample/ApiGeneratorSampleApp/Program.cs
+++ b/sample/ApiGeneratorSampleApp/Program.cs
@@ -1,15 +1,16 @@
+using System.Reflection;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
-using System.Configuration;
-using System.Reflection;
-using TCDev.ApiGenerator.Data;
using TCDev.ApiGenerator.Extension;
+using TCDev.APIGenerator.Identity;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
+
+builder.Services.AddApiGeneratorIdentity(builder.Configuration);
builder.Services.AddApiGeneratorServices(builder.Configuration, Assembly.GetExecutingAssembly());
var app = builder.Build();
@@ -17,16 +18,16 @@
// Configure the HTTP request pipeline.
app.UseApiGenerator();
-app.UseAutomaticAPIMigrations(true);
+app.UseAutomaticApiMigrations(true);
app.UseHttpsRedirection();
-
+app.UseStaticFiles();
app.UseRouting();
-app.UseAuthentication();
-app.UseAuthorization();
+app.UseApiGeneratorAuthentication();
-app.UseEndpoints(endpoints => {
+app.UseEndpoints(endpoints =>
+{
endpoints.UseApiGeneratorEndpoints();
endpoints.MapControllers();
});
diff --git a/sample/ApiGeneratorSampleApp/Properties/ServiceDependencies/apigenerator/apis1.arm.json b/sample/ApiGeneratorSampleApp/Properties/ServiceDependencies/apigenerator/apis1.arm.json
new file mode 100644
index 0000000..7dd1f08
--- /dev/null
+++ b/sample/ApiGeneratorSampleApp/Properties/ServiceDependencies/apigenerator/apis1.arm.json
@@ -0,0 +1,131 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "resourceGroupName": {
+ "type": "string",
+ "defaultValue": "apigenerator_group",
+ "metadata": {
+ "_parameterType": "resourceGroup",
+ "description": "Der Name der Ressourcengruppe für die Ressource. Es wird empfohlen, Ressourcen für eine bessere Nachverfolgung in derselben Ressourcengruppe zu platzieren."
+ }
+ },
+ "resourceGroupLocation": {
+ "type": "string",
+ "defaultValue": "westeurope",
+ "metadata": {
+ "_parameterType": "location",
+ "description": "Der Standort der Ressourcengruppe. Ressourcengruppen können andere Standorte als Ressourcen aufweisen."
+ }
+ },
+ "resourceLocation": {
+ "type": "string",
+ "defaultValue": "[parameters('resourceGroupLocation')]",
+ "metadata": {
+ "_parameterType": "location",
+ "description": "Der Standort der Ressource. Verwenden Sie standardmäßig den Standort der Ressourcengruppe, sofern der Ressourcenanbieter dort unterstützt wird."
+ }
+ }
+ },
+ "resources": [
+ {
+ "type": "Microsoft.Resources/resourceGroups",
+ "name": "[parameters('resourceGroupName')]",
+ "location": "[parameters('resourceGroupLocation')]",
+ "apiVersion": "2019-10-01"
+ },
+ {
+ "type": "Microsoft.Resources/deployments",
+ "name": "[concat(parameters('resourceGroupName'), 'Deployment', uniqueString(concat('ApiGeneratorSampleApI', subscription().subscriptionId)))]",
+ "resourceGroup": "[parameters('resourceGroupName')]",
+ "apiVersion": "2019-10-01",
+ "dependsOn": [
+ "[parameters('resourceGroupName')]"
+ ],
+ "properties": {
+ "mode": "Incremental",
+ "template": {
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "resources": [
+ {
+ "name": "ApiGeneratorSampleApIapi",
+ "type": "Microsoft.ApiManagement/service",
+ "location": "[parameters('resourceLocation')]",
+ "properties": {
+ "publisherEmail": "tim.cadenbach@outlook.com",
+ "publisherName": "Tim Cadenbach",
+ "notificationSenderEmail": "apimgmt-noreply@mail.windowsazure.com",
+ "hostnameConfigurations": [
+ {
+ "type": "Proxy",
+ "hostName": "apigeneratorsampleapiapi.azure-api.net",
+ "encodedCertificate": null,
+ "keyVaultId": null,
+ "certificatePassword": null,
+ "negotiateClientCertificate": false,
+ "certificate": null,
+ "defaultSslBinding": true
+ }
+ ],
+ "publicIPAddresses": null,
+ "privateIPAddresses": null,
+ "additionalLocations": null,
+ "virtualNetworkConfiguration": null,
+ "customProperties": {
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls10": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls11": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Ssl30": "False",
+ "Microsoft.WindowsAzure.ApiManagement.Gateway.Protocols.Server.Http2": "False"
+ },
+ "virtualNetworkType": "None",
+ "certificates": null,
+ "apiVersionConstraint": {
+ "minApiVersion": null
+ }
+ },
+ "sku": {
+ "name": "Consumption",
+ "capacity": 0
+ },
+ "apiVersion": "2019-12-01"
+ },
+ {
+ "type": "Microsoft.ApiManagement/service/apis",
+ "name": "ApiGeneratorSampleApIapi/ApiGeneratorSampleApI",
+ "properties": {
+ "displayName": "ApiGeneratorSampleApI",
+ "apiRevision": "1",
+ "description": null,
+ "subscriptionRequired": true,
+ "serviceUrl": null,
+ "path": "generated",
+ "protocols": [
+ "https"
+ ],
+ "authenticationSettings": {
+ "oAuth2": null,
+ "openid": null
+ },
+ "subscriptionKeyParameterNames": {
+ "header": "Ocp-Apim-Subscription-Key",
+ "query": "subscription-key"
+ },
+ "isCurrent": true
+ },
+ "apiVersion": "2019-12-01",
+ "dependsOn": [
+ "ApiGeneratorSampleApIapi"
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "metadata": {
+ "_dependencyType": "apis.azure"
+ }
+}
\ No newline at end of file
diff --git a/sample/ApiGeneratorSampleApp/Properties/serviceDependencies.apigenerator.json b/sample/ApiGeneratorSampleApp/Properties/serviceDependencies.apigenerator.json
new file mode 100644
index 0000000..74df1f5
--- /dev/null
+++ b/sample/ApiGeneratorSampleApp/Properties/serviceDependencies.apigenerator.json
@@ -0,0 +1,8 @@
+{
+ "dependencies": {
+ "apis1": {
+ "resourceId": "/subscriptions/[parameters('subscriptionId')]/resourceGroups/[parameters('resourceGroupName')]/providers/Microsoft.ApiManagement/service/ApiGeneratorSampleApIapi/apis/ApiGeneratorSampleApI",
+ "type": "apis.azure"
+ }
+ }
+}
\ No newline at end of file
diff --git a/sample/ApiGeneratorSampleApp/Properties/serviceDependencies.json b/sample/ApiGeneratorSampleApp/Properties/serviceDependencies.json
new file mode 100644
index 0000000..e32266d
--- /dev/null
+++ b/sample/ApiGeneratorSampleApp/Properties/serviceDependencies.json
@@ -0,0 +1,7 @@
+{
+ "dependencies": {
+ "apis1": {
+ "type": "apis"
+ }
+ }
+}
\ No newline at end of file
diff --git a/sample/ApiGeneratorSampleApp/appsettings.json b/sample/ApiGeneratorSampleApp/appsettings.json
index d9adf60..ae045f9 100644
--- a/sample/ApiGeneratorSampleApp/appsettings.json
+++ b/sample/ApiGeneratorSampleApp/appsettings.json
@@ -25,25 +25,22 @@ For more info see https://aka.ms/dotnet-template-ms-identity-platform
"AllowedHosts": "*",
//Sample Config for API Generator
"Api": {
- "Database": {
- "DatabaseType": "SQL"
- }
- }
"Swagger": {
"EnableProduction": "false", // Enable/Disable for production builds
- "Description": "Sample Swagger Config",
+ "Description": "Smoower API Sample",
"Version": "v1",
- "Title": "Sample Swagger Config Title",
+ "Title": "Smoower sample config",
"ContactMail": "Me@me.de",
"ContactUri": "https://www.myuri.com"
},
"Database": {
- "DatabaseType": "SQL|InMemory|SQLite"
+ "DatabaseType": "InMemory"
},
"Odata": {
+ "Enabled": true,
"EnableSelect": true,
- "EnableFilter": true,
- "EnableSort": true
+ "EnableFilter": false,
+ "EnableSort": false
}
}
}
diff --git a/sample/ApiGeneratorSampleApp/wwwroot/SwaggerDarkTheme.css b/sample/ApiGeneratorSampleApp/wwwroot/SwaggerDarkTheme.css
new file mode 100644
index 0000000..fe9c05f
--- /dev/null
+++ b/sample/ApiGeneratorSampleApp/wwwroot/SwaggerDarkTheme.css
@@ -0,0 +1,1329 @@
+a {
+ color: #8c8cfa;
+}
+
+::-webkit-scrollbar-track-piece {
+ background-color: rgba(255, 255, 255, .2) !important;
+}
+
+::-webkit-scrollbar-track {
+ background-color: rgba(255, 255, 255, .3) !important;
+}
+
+::-webkit-scrollbar-thumb {
+ background-color: rgba(255, 255, 255, .5) !important;
+}
+
+embed[type="application/pdf"] {
+ filter: invert(90%);
+}
+
+html {
+ background: #1f1f1f !important;
+ box-sizing: border-box;
+ filter: contrast(100%) brightness(100%) saturate(100%);
+ overflow-y: scroll;
+}
+
+body {
+ background: #1f1f1f;
+ background-color: #1f1f1f;
+ background-image: none !important;
+}
+
+button, input, select, textarea {
+ background-color: #1f1f1f;
+ color: #bfbfbf;
+}
+
+font, html {
+ color: #bfbfbf;
+}
+
+.swagger-ui, .swagger-ui section h3 {
+ color: #b5bac9;
+}
+
+ .swagger-ui a {
+ background-color: transparent;
+ }
+
+ .swagger-ui mark {
+ background-color: #664b00;
+ color: #bfbfbf;
+ }
+
+ .swagger-ui legend {
+ color: inherit;
+ }
+
+ .swagger-ui .debug * {
+ outline: #e6da99 solid 1px;
+ }
+
+ .swagger-ui .debug-white * {
+ outline: #fff solid 1px;
+ }
+
+ .swagger-ui .debug-black * {
+ outline: #bfbfbf solid 1px;
+ }
+
+ .swagger-ui .debug-grid {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) 0 0;
+ }
+
+ .swagger-ui .debug-grid-16 {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODYyRjhERDU2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODYyRjhERDQ2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QTY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3QjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvCS01IAAABMSURBVHjaYmR4/5+BFPBfAMFm/MBgx8RAGWCn1AAmSg34Q6kBDKMGMDCwICeMIemF/5QawEipAWwUhwEjMDvbAWlWkvVBwu8vQIABAEwBCph8U6c0AAAAAElFTkSuQmCC) 0 0;
+ }
+
+ .swagger-ui .debug-grid-8-solid {
+ background: url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAAAAD/4QMxaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzExMSA3OS4xNTgzMjUsIDIwMTUvMDkvMTAtMDE6MTA6MjAgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkIxMjI0OTczNjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkIxMjI0OTc0NjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QjEyMjQ5NzE2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjEyMjQ5NzI2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAbGhopHSlBJiZBQi8vL0JHPz4+P0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHAR0pKTQmND8oKD9HPzU/R0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAAIAAgDASIAAhEBAxEB/8QAWQABAQAAAAAAAAAAAAAAAAAAAAYBAQEAAAAAAAAAAAAAAAAAAAIEEAEBAAMBAAAAAAAAAAAAAAABADECA0ERAAEDBQAAAAAAAAAAAAAAAAARITFBUWESIv/aAAwDAQACEQMRAD8AoOnTV1QTD7JJshP3vSM3P//Z) 0 0 #1c1c21;
+ }
+
+ .swagger-ui .debug-grid-16-solid {
+ background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzY3MkJEN0U2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzY3MkJEN0Y2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3RDY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pve6J3kAAAAzSURBVHjaYvz//z8D0UDsMwMjSRoYP5Gq4SPNbRjVMEQ1fCRDg+in/6+J1AJUxsgAEGAA31BAJMS0GYEAAAAASUVORK5CYII=) 0 0 #1c1c21;
+ }
+
+ .swagger-ui .b--black {
+ border-color: #000;
+ }
+
+ .swagger-ui .b--near-black {
+ border-color: #121212;
+ }
+
+ .swagger-ui .b--dark-gray {
+ border-color: #333;
+ }
+
+ .swagger-ui .b--mid-gray {
+ border-color: #545454;
+ }
+
+ .swagger-ui .b--gray {
+ border-color: #787878;
+ }
+
+ .swagger-ui .b--silver {
+ border-color: #999;
+ }
+
+ .swagger-ui .b--light-silver {
+ border-color: #6e6e6e;
+ }
+
+ .swagger-ui .b--moon-gray {
+ border-color: #4d4d4d;
+ }
+
+ .swagger-ui .b--light-gray {
+ border-color: #2b2b2b;
+ }
+
+ .swagger-ui .b--near-white {
+ border-color: #242424;
+ }
+
+ .swagger-ui .b--white {
+ border-color: #1c1c21;
+ }
+
+ .swagger-ui .b--white-90 {
+ border-color: rgba(28, 28, 33, .9);
+ }
+
+ .swagger-ui .b--white-80 {
+ border-color: rgba(28, 28, 33, .8);
+ }
+
+ .swagger-ui .b--white-70 {
+ border-color: rgba(28, 28, 33, .7);
+ }
+
+ .swagger-ui .b--white-60 {
+ border-color: rgba(28, 28, 33, .6);
+ }
+
+ .swagger-ui .b--white-50 {
+ border-color: rgba(28, 28, 33, .5);
+ }
+
+ .swagger-ui .b--white-40 {
+ border-color: rgba(28, 28, 33, .4);
+ }
+
+ .swagger-ui .b--white-30 {
+ border-color: rgba(28, 28, 33, .3);
+ }
+
+ .swagger-ui .b--white-20 {
+ border-color: rgba(28, 28, 33, .2);
+ }
+
+ .swagger-ui .b--white-10 {
+ border-color: rgba(28, 28, 33, .1);
+ }
+
+ .swagger-ui .b--white-05 {
+ border-color: rgba(28, 28, 33, .05);
+ }
+
+ .swagger-ui .b--white-025 {
+ border-color: rgba(28, 28, 33, .024);
+ }
+
+ .swagger-ui .b--white-0125 {
+ border-color: rgba(28, 28, 33, .01);
+ }
+
+ .swagger-ui .b--black-90 {
+ border-color: rgba(0, 0, 0, .9);
+ }
+
+ .swagger-ui .b--black-80 {
+ border-color: rgba(0, 0, 0, .8);
+ }
+
+ .swagger-ui .b--black-70 {
+ border-color: rgba(0, 0, 0, .7);
+ }
+
+ .swagger-ui .b--black-60 {
+ border-color: rgba(0, 0, 0, .6);
+ }
+
+ .swagger-ui .b--black-50 {
+ border-color: rgba(0, 0, 0, .5);
+ }
+
+ .swagger-ui .b--black-40 {
+ border-color: rgba(0, 0, 0, .4);
+ }
+
+ .swagger-ui .b--black-30 {
+ border-color: rgba(0, 0, 0, .3);
+ }
+
+ .swagger-ui .b--black-20 {
+ border-color: rgba(0, 0, 0, .2);
+ }
+
+ .swagger-ui .b--black-10 {
+ border-color: rgba(0, 0, 0, .1);
+ }
+
+ .swagger-ui .b--black-05 {
+ border-color: rgba(0, 0, 0, .05);
+ }
+
+ .swagger-ui .b--black-025 {
+ border-color: rgba(0, 0, 0, .024);
+ }
+
+ .swagger-ui .b--black-0125 {
+ border-color: rgba(0, 0, 0, .01);
+ }
+
+ .swagger-ui .b--dark-red {
+ border-color: #bc2f36;
+ }
+
+ .swagger-ui .b--red {
+ border-color: #c83932;
+ }
+
+ .swagger-ui .b--light-red {
+ border-color: #ab3c2b;
+ }
+
+ .swagger-ui .b--orange {
+ border-color: #cc6e33;
+ }
+
+ .swagger-ui .b--purple {
+ border-color: #5e2ca5;
+ }
+
+ .swagger-ui .b--light-purple {
+ border-color: #672caf;
+ }
+
+ .swagger-ui .b--dark-pink {
+ border-color: #ab2b81;
+ }
+
+ .swagger-ui .b--hot-pink {
+ border-color: #c03086;
+ }
+
+ .swagger-ui .b--pink {
+ border-color: #8f2464;
+ }
+
+ .swagger-ui .b--light-pink {
+ border-color: #721d4d;
+ }
+
+ .swagger-ui .b--dark-green {
+ border-color: #1c6e50;
+ }
+
+ .swagger-ui .b--green {
+ border-color: #279b70;
+ }
+
+ .swagger-ui .b--light-green {
+ border-color: #228762;
+ }
+
+ .swagger-ui .b--navy {
+ border-color: #0d1d35;
+ }
+
+ .swagger-ui .b--dark-blue {
+ border-color: #20497e;
+ }
+
+ .swagger-ui .b--blue {
+ border-color: #4380d0;
+ }
+
+ .swagger-ui .b--light-blue {
+ border-color: #20517e;
+ }
+
+ .swagger-ui .b--lightest-blue {
+ border-color: #143a52;
+ }
+
+ .swagger-ui .b--washed-blue {
+ border-color: #0c312d;
+ }
+
+ .swagger-ui .b--washed-green {
+ border-color: #0f3d2c;
+ }
+
+ .swagger-ui .b--washed-red {
+ border-color: #411010;
+ }
+
+ .swagger-ui .b--transparent {
+ border-color: transparent;
+ }
+
+ .swagger-ui .b--gold, .swagger-ui .b--light-yellow, .swagger-ui .b--washed-yellow, .swagger-ui .b--yellow {
+ border-color: #664b00;
+ }
+
+ .swagger-ui .shadow-1 {
+ box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px;
+ }
+
+ .swagger-ui .shadow-2 {
+ box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px;
+ }
+
+ .swagger-ui .shadow-3 {
+ box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px;
+ }
+
+ .swagger-ui .shadow-4 {
+ box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0;
+ }
+
+ .swagger-ui .shadow-5 {
+ box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0;
+ }
+
+@media screen and (min-width: 30em) {
+ .swagger-ui .shadow-1-ns {
+ box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px;
+ }
+
+ .swagger-ui .shadow-2-ns {
+ box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px;
+ }
+
+ .swagger-ui .shadow-3-ns {
+ box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px;
+ }
+
+ .swagger-ui .shadow-4-ns {
+ box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0;
+ }
+
+ .swagger-ui .shadow-5-ns {
+ box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0;
+ }
+}
+
+@media screen and (max-width: 60em) and (min-width: 30em) {
+ .swagger-ui .shadow-1-m {
+ box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px;
+ }
+
+ .swagger-ui .shadow-2-m {
+ box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px;
+ }
+
+ .swagger-ui .shadow-3-m {
+ box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px;
+ }
+
+ .swagger-ui .shadow-4-m {
+ box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0;
+ }
+
+ .swagger-ui .shadow-5-m {
+ box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0;
+ }
+}
+
+@media screen and (min-width: 60em) {
+ .swagger-ui .shadow-1-l {
+ box-shadow: rgba(0, 0, 0, .2) 0 0 4px 2px;
+ }
+
+ .swagger-ui .shadow-2-l {
+ box-shadow: rgba(0, 0, 0, .2) 0 0 8px 2px;
+ }
+
+ .swagger-ui .shadow-3-l {
+ box-shadow: rgba(0, 0, 0, .2) 2px 2px 4px 2px;
+ }
+
+ .swagger-ui .shadow-4-l {
+ box-shadow: rgba(0, 0, 0, .2) 2px 2px 8px 0;
+ }
+
+ .swagger-ui .shadow-5-l {
+ box-shadow: rgba(0, 0, 0, .2) 4px 4px 8px 0;
+ }
+}
+
+.swagger-ui .black-05 {
+ color: rgba(191, 191, 191, .05);
+}
+
+.swagger-ui .bg-black-05 {
+ background-color: rgba(0, 0, 0, .05);
+}
+
+.swagger-ui .black-90, .swagger-ui .hover-black-90:focus, .swagger-ui .hover-black-90:hover {
+ color: rgba(191, 191, 191, .9);
+}
+
+.swagger-ui .black-80, .swagger-ui .hover-black-80:focus, .swagger-ui .hover-black-80:hover {
+ color: rgba(191, 191, 191, .8);
+}
+
+.swagger-ui .black-70, .swagger-ui .hover-black-70:focus, .swagger-ui .hover-black-70:hover {
+ color: rgba(191, 191, 191, .7);
+}
+
+.swagger-ui .black-60, .swagger-ui .hover-black-60:focus, .swagger-ui .hover-black-60:hover {
+ color: rgba(191, 191, 191, .6);
+}
+
+.swagger-ui .black-50, .swagger-ui .hover-black-50:focus, .swagger-ui .hover-black-50:hover {
+ color: rgba(191, 191, 191, .5);
+}
+
+.swagger-ui .black-40, .swagger-ui .hover-black-40:focus, .swagger-ui .hover-black-40:hover {
+ color: rgba(191, 191, 191, .4);
+}
+
+.swagger-ui .black-30, .swagger-ui .hover-black-30:focus, .swagger-ui .hover-black-30:hover {
+ color: rgba(191, 191, 191, .3);
+}
+
+.swagger-ui .black-20, .swagger-ui .hover-black-20:focus, .swagger-ui .hover-black-20:hover {
+ color: rgba(191, 191, 191, .2);
+}
+
+.swagger-ui .black-10, .swagger-ui .hover-black-10:focus, .swagger-ui .hover-black-10:hover {
+ color: rgba(191, 191, 191, .1);
+}
+
+.swagger-ui .hover-white-90:focus, .swagger-ui .hover-white-90:hover, .swagger-ui .white-90 {
+ color: rgba(255, 255, 255, .9);
+}
+
+.swagger-ui .hover-white-80:focus, .swagger-ui .hover-white-80:hover, .swagger-ui .white-80 {
+ color: rgba(255, 255, 255, .8);
+}
+
+.swagger-ui .hover-white-70:focus, .swagger-ui .hover-white-70:hover, .swagger-ui .white-70 {
+ color: rgba(255, 255, 255, .7);
+}
+
+.swagger-ui .hover-white-60:focus, .swagger-ui .hover-white-60:hover, .swagger-ui .white-60 {
+ color: rgba(255, 255, 255, .6);
+}
+
+.swagger-ui .hover-white-50:focus, .swagger-ui .hover-white-50:hover, .swagger-ui .white-50 {
+ color: rgba(255, 255, 255, .5);
+}
+
+.swagger-ui .hover-white-40:focus, .swagger-ui .hover-white-40:hover, .swagger-ui .white-40 {
+ color: rgba(255, 255, 255, .4);
+}
+
+.swagger-ui .hover-white-30:focus, .swagger-ui .hover-white-30:hover, .swagger-ui .white-30 {
+ color: rgba(255, 255, 255, .3);
+}
+
+.swagger-ui .hover-white-20:focus, .swagger-ui .hover-white-20:hover, .swagger-ui .white-20 {
+ color: rgba(255, 255, 255, .2);
+}
+
+.swagger-ui .hover-white-10:focus, .swagger-ui .hover-white-10:hover, .swagger-ui .white-10 {
+ color: rgba(255, 255, 255, .1);
+}
+
+.swagger-ui .hover-moon-gray:focus, .swagger-ui .hover-moon-gray:hover, .swagger-ui .moon-gray {
+ color: #ccc;
+}
+
+.swagger-ui .hover-light-gray:focus, .swagger-ui .hover-light-gray:hover, .swagger-ui .light-gray {
+ color: #ededed;
+}
+
+.swagger-ui .hover-near-white:focus, .swagger-ui .hover-near-white:hover, .swagger-ui .near-white {
+ color: #f5f5f5;
+}
+
+.swagger-ui .dark-red, .swagger-ui .hover-dark-red:focus, .swagger-ui .hover-dark-red:hover {
+ color: #e6999d;
+}
+
+.swagger-ui .hover-red:focus, .swagger-ui .hover-red:hover, .swagger-ui .red {
+ color: #e69d99;
+}
+
+.swagger-ui .hover-light-red:focus, .swagger-ui .hover-light-red:hover, .swagger-ui .light-red {
+ color: #e6a399;
+}
+
+.swagger-ui .hover-orange:focus, .swagger-ui .hover-orange:hover, .swagger-ui .orange {
+ color: #e6b699;
+}
+
+.swagger-ui .gold, .swagger-ui .hover-gold:focus, .swagger-ui .hover-gold:hover {
+ color: #e6d099;
+}
+
+.swagger-ui .hover-yellow:focus, .swagger-ui .hover-yellow:hover, .swagger-ui .yellow {
+ color: #e6da99;
+}
+
+.swagger-ui .hover-light-yellow:focus, .swagger-ui .hover-light-yellow:hover, .swagger-ui .light-yellow {
+ color: #ede6b6;
+}
+
+.swagger-ui .hover-purple:focus, .swagger-ui .hover-purple:hover, .swagger-ui .purple {
+ color: #b99ae4;
+}
+
+.swagger-ui .hover-light-purple:focus, .swagger-ui .hover-light-purple:hover, .swagger-ui .light-purple {
+ color: #bb99e6;
+}
+
+.swagger-ui .dark-pink, .swagger-ui .hover-dark-pink:focus, .swagger-ui .hover-dark-pink:hover {
+ color: #e699cc;
+}
+
+.swagger-ui .hot-pink, .swagger-ui .hover-hot-pink:focus, .swagger-ui .hover-hot-pink:hover, .swagger-ui .hover-pink:focus, .swagger-ui .hover-pink:hover, .swagger-ui .pink {
+ color: #e699c7;
+}
+
+.swagger-ui .hover-light-pink:focus, .swagger-ui .hover-light-pink:hover, .swagger-ui .light-pink {
+ color: #edb6d5;
+}
+
+.swagger-ui .dark-green, .swagger-ui .green, .swagger-ui .hover-dark-green:focus, .swagger-ui .hover-dark-green:hover, .swagger-ui .hover-green:focus, .swagger-ui .hover-green:hover {
+ color: #99e6c9;
+}
+
+.swagger-ui .hover-light-green:focus, .swagger-ui .hover-light-green:hover, .swagger-ui .light-green {
+ color: #a1e8ce;
+}
+
+.swagger-ui .hover-navy:focus, .swagger-ui .hover-navy:hover, .swagger-ui .navy {
+ color: #99b8e6;
+}
+
+.swagger-ui .blue, .swagger-ui .dark-blue, .swagger-ui .hover-blue:focus, .swagger-ui .hover-blue:hover, .swagger-ui .hover-dark-blue:focus, .swagger-ui .hover-dark-blue:hover {
+ color: #99bae6;
+}
+
+.swagger-ui .hover-light-blue:focus, .swagger-ui .hover-light-blue:hover, .swagger-ui .light-blue {
+ color: #a9cbea;
+}
+
+.swagger-ui .hover-lightest-blue:focus, .swagger-ui .hover-lightest-blue:hover, .swagger-ui .lightest-blue {
+ color: #d6e9f5;
+}
+
+.swagger-ui .hover-washed-blue:focus, .swagger-ui .hover-washed-blue:hover, .swagger-ui .washed-blue {
+ color: #f7fdfc;
+}
+
+.swagger-ui .hover-washed-green:focus, .swagger-ui .hover-washed-green:hover, .swagger-ui .washed-green {
+ color: #ebfaf4;
+}
+
+.swagger-ui .hover-washed-yellow:focus, .swagger-ui .hover-washed-yellow:hover, .swagger-ui .washed-yellow {
+ color: #fbf9ef;
+}
+
+.swagger-ui .hover-washed-red:focus, .swagger-ui .hover-washed-red:hover, .swagger-ui .washed-red {
+ color: #f9e7e7;
+}
+
+.swagger-ui .color-inherit, .swagger-ui .hover-inherit:focus, .swagger-ui .hover-inherit:hover {
+ color: inherit;
+}
+
+.swagger-ui .bg-black-90, .swagger-ui .hover-bg-black-90:focus, .swagger-ui .hover-bg-black-90:hover {
+ background-color: rgba(0, 0, 0, .9);
+}
+
+.swagger-ui .bg-black-80, .swagger-ui .hover-bg-black-80:focus, .swagger-ui .hover-bg-black-80:hover {
+ background-color: rgba(0, 0, 0, .8);
+}
+
+.swagger-ui .bg-black-70, .swagger-ui .hover-bg-black-70:focus, .swagger-ui .hover-bg-black-70:hover {
+ background-color: rgba(0, 0, 0, .7);
+}
+
+.swagger-ui .bg-black-60, .swagger-ui .hover-bg-black-60:focus, .swagger-ui .hover-bg-black-60:hover {
+ background-color: rgba(0, 0, 0, .6);
+}
+
+.swagger-ui .bg-black-50, .swagger-ui .hover-bg-black-50:focus, .swagger-ui .hover-bg-black-50:hover {
+ background-color: rgba(0, 0, 0, .5);
+}
+
+.swagger-ui .bg-black-40, .swagger-ui .hover-bg-black-40:focus, .swagger-ui .hover-bg-black-40:hover {
+ background-color: rgba(0, 0, 0, .4);
+}
+
+.swagger-ui .bg-black-30, .swagger-ui .hover-bg-black-30:focus, .swagger-ui .hover-bg-black-30:hover {
+ background-color: rgba(0, 0, 0, .3);
+}
+
+.swagger-ui .bg-black-20, .swagger-ui .hover-bg-black-20:focus, .swagger-ui .hover-bg-black-20:hover {
+ background-color: rgba(0, 0, 0, .2);
+}
+
+.swagger-ui .bg-white-90, .swagger-ui .hover-bg-white-90:focus, .swagger-ui .hover-bg-white-90:hover {
+ background-color: rgba(28, 28, 33, .9);
+}
+
+.swagger-ui .bg-white-80, .swagger-ui .hover-bg-white-80:focus, .swagger-ui .hover-bg-white-80:hover {
+ background-color: rgba(28, 28, 33, .8);
+}
+
+.swagger-ui .bg-white-70, .swagger-ui .hover-bg-white-70:focus, .swagger-ui .hover-bg-white-70:hover {
+ background-color: rgba(28, 28, 33, .7);
+}
+
+.swagger-ui .bg-white-60, .swagger-ui .hover-bg-white-60:focus, .swagger-ui .hover-bg-white-60:hover {
+ background-color: rgba(28, 28, 33, .6);
+}
+
+.swagger-ui .bg-white-50, .swagger-ui .hover-bg-white-50:focus, .swagger-ui .hover-bg-white-50:hover {
+ background-color: rgba(28, 28, 33, .5);
+}
+
+.swagger-ui .bg-white-40, .swagger-ui .hover-bg-white-40:focus, .swagger-ui .hover-bg-white-40:hover {
+ background-color: rgba(28, 28, 33, .4);
+}
+
+.swagger-ui .bg-white-30, .swagger-ui .hover-bg-white-30:focus, .swagger-ui .hover-bg-white-30:hover {
+ background-color: rgba(28, 28, 33, .3);
+}
+
+.swagger-ui .bg-white-20, .swagger-ui .hover-bg-white-20:focus, .swagger-ui .hover-bg-white-20:hover {
+ background-color: rgba(28, 28, 33, .2);
+}
+
+.swagger-ui .bg-black, .swagger-ui .hover-bg-black:focus, .swagger-ui .hover-bg-black:hover {
+ background-color: #000;
+}
+
+.swagger-ui .bg-near-black, .swagger-ui .hover-bg-near-black:focus, .swagger-ui .hover-bg-near-black:hover {
+ background-color: #121212;
+}
+
+.swagger-ui .bg-dark-gray, .swagger-ui .hover-bg-dark-gray:focus, .swagger-ui .hover-bg-dark-gray:hover {
+ background-color: #333;
+}
+
+.swagger-ui .bg-mid-gray, .swagger-ui .hover-bg-mid-gray:focus, .swagger-ui .hover-bg-mid-gray:hover {
+ background-color: #545454;
+}
+
+.swagger-ui .bg-gray, .swagger-ui .hover-bg-gray:focus, .swagger-ui .hover-bg-gray:hover {
+ background-color: #787878;
+}
+
+.swagger-ui .bg-silver, .swagger-ui .hover-bg-silver:focus, .swagger-ui .hover-bg-silver:hover {
+ background-color: #999;
+}
+
+.swagger-ui .bg-white, .swagger-ui .hover-bg-white:focus, .swagger-ui .hover-bg-white:hover {
+ background-color: #1c1c21;
+}
+
+.swagger-ui .bg-transparent, .swagger-ui .hover-bg-transparent:focus, .swagger-ui .hover-bg-transparent:hover {
+ background-color: transparent;
+}
+
+.swagger-ui .bg-dark-red, .swagger-ui .hover-bg-dark-red:focus, .swagger-ui .hover-bg-dark-red:hover {
+ background-color: #bc2f36;
+}
+
+.swagger-ui .bg-red, .swagger-ui .hover-bg-red:focus, .swagger-ui .hover-bg-red:hover {
+ background-color: #c83932;
+}
+
+.swagger-ui .bg-light-red, .swagger-ui .hover-bg-light-red:focus, .swagger-ui .hover-bg-light-red:hover {
+ background-color: #ab3c2b;
+}
+
+.swagger-ui .bg-orange, .swagger-ui .hover-bg-orange:focus, .swagger-ui .hover-bg-orange:hover {
+ background-color: #cc6e33;
+}
+
+.swagger-ui .bg-gold, .swagger-ui .bg-light-yellow, .swagger-ui .bg-washed-yellow, .swagger-ui .bg-yellow, .swagger-ui .hover-bg-gold:focus, .swagger-ui .hover-bg-gold:hover, .swagger-ui .hover-bg-light-yellow:focus, .swagger-ui .hover-bg-light-yellow:hover, .swagger-ui .hover-bg-washed-yellow:focus, .swagger-ui .hover-bg-washed-yellow:hover, .swagger-ui .hover-bg-yellow:focus, .swagger-ui .hover-bg-yellow:hover {
+ background-color: #664b00;
+}
+
+.swagger-ui .bg-purple, .swagger-ui .hover-bg-purple:focus, .swagger-ui .hover-bg-purple:hover {
+ background-color: #5e2ca5;
+}
+
+.swagger-ui .bg-light-purple, .swagger-ui .hover-bg-light-purple:focus, .swagger-ui .hover-bg-light-purple:hover {
+ background-color: #672caf;
+}
+
+.swagger-ui .bg-dark-pink, .swagger-ui .hover-bg-dark-pink:focus, .swagger-ui .hover-bg-dark-pink:hover {
+ background-color: #ab2b81;
+}
+
+.swagger-ui .bg-hot-pink, .swagger-ui .hover-bg-hot-pink:focus, .swagger-ui .hover-bg-hot-pink:hover {
+ background-color: #c03086;
+}
+
+.swagger-ui .bg-pink, .swagger-ui .hover-bg-pink:focus, .swagger-ui .hover-bg-pink:hover {
+ background-color: #8f2464;
+}
+
+.swagger-ui .bg-light-pink, .swagger-ui .hover-bg-light-pink:focus, .swagger-ui .hover-bg-light-pink:hover {
+ background-color: #721d4d;
+}
+
+.swagger-ui .bg-dark-green, .swagger-ui .hover-bg-dark-green:focus, .swagger-ui .hover-bg-dark-green:hover {
+ background-color: #1c6e50;
+}
+
+.swagger-ui .bg-green, .swagger-ui .hover-bg-green:focus, .swagger-ui .hover-bg-green:hover {
+ background-color: #279b70;
+}
+
+.swagger-ui .bg-light-green, .swagger-ui .hover-bg-light-green:focus, .swagger-ui .hover-bg-light-green:hover {
+ background-color: #228762;
+}
+
+.swagger-ui .bg-navy, .swagger-ui .hover-bg-navy:focus, .swagger-ui .hover-bg-navy:hover {
+ background-color: #0d1d35;
+}
+
+.swagger-ui .bg-dark-blue, .swagger-ui .hover-bg-dark-blue:focus, .swagger-ui .hover-bg-dark-blue:hover {
+ background-color: #20497e;
+}
+
+.swagger-ui .bg-blue, .swagger-ui .hover-bg-blue:focus, .swagger-ui .hover-bg-blue:hover {
+ background-color: #4380d0;
+}
+
+.swagger-ui .bg-light-blue, .swagger-ui .hover-bg-light-blue:focus, .swagger-ui .hover-bg-light-blue:hover {
+ background-color: #20517e;
+}
+
+.swagger-ui .bg-lightest-blue, .swagger-ui .hover-bg-lightest-blue:focus, .swagger-ui .hover-bg-lightest-blue:hover {
+ background-color: #143a52;
+}
+
+.swagger-ui .bg-washed-blue, .swagger-ui .hover-bg-washed-blue:focus, .swagger-ui .hover-bg-washed-blue:hover {
+ background-color: #0c312d;
+}
+
+.swagger-ui .bg-washed-green, .swagger-ui .hover-bg-washed-green:focus, .swagger-ui .hover-bg-washed-green:hover {
+ background-color: #0f3d2c;
+}
+
+.swagger-ui .bg-washed-red, .swagger-ui .hover-bg-washed-red:focus, .swagger-ui .hover-bg-washed-red:hover {
+ background-color: #411010;
+}
+
+.swagger-ui .bg-inherit, .swagger-ui .hover-bg-inherit:focus, .swagger-ui .hover-bg-inherit:hover {
+ background-color: inherit;
+}
+
+.swagger-ui .shadow-hover {
+ transition: all .5s cubic-bezier(.165, .84, .44, 1) 0s;
+}
+
+ .swagger-ui .shadow-hover::after {
+ border-radius: inherit;
+ box-shadow: rgba(0, 0, 0, .2) 0 0 16px 2px;
+ content: "";
+ height: 100%;
+ left: 0;
+ opacity: 0;
+ position: absolute;
+ top: 0;
+ transition: opacity .5s cubic-bezier(.165, .84, .44, 1) 0s;
+ width: 100%;
+ z-index: -1;
+ }
+
+.swagger-ui .bg-animate, .swagger-ui .bg-animate:focus, .swagger-ui .bg-animate:hover {
+ transition: background-color .15s ease-in-out 0s;
+}
+
+.swagger-ui .nested-links a {
+ color: #99bae6;
+ transition: color .15s ease-in 0s;
+}
+
+ .swagger-ui .nested-links a:focus, .swagger-ui .nested-links a:hover {
+ color: #a9cbea;
+ transition: color .15s ease-in 0s;
+ }
+
+.swagger-ui .opblock-tag {
+ border-bottom: 1px solid rgba(58, 64, 80, .3);
+ color: #b5bac9;
+ transition: all .2s ease 0s;
+}
+
+ .swagger-ui .opblock-tag svg, .swagger-ui section.models h4 svg {
+ transition: all .4s ease 0s;
+ }
+
+.swagger-ui .opblock {
+ border: 1px solid #000;
+ border-radius: 4px;
+ box-shadow: rgba(0, 0, 0, .19) 0 0 3px;
+ margin: 0 0 15px;
+}
+
+ .swagger-ui .opblock .tab-header .tab-item.active h4 span::after {
+ background: gray;
+ }
+
+ .swagger-ui .opblock.is-open .opblock-summary {
+ border-bottom: 1px solid #000;
+ }
+
+ .swagger-ui .opblock .opblock-section-header {
+ background: rgba(28, 28, 33, .8);
+ box-shadow: rgba(0, 0, 0, .1) 0 1px 2px;
+ }
+
+ .swagger-ui .opblock .opblock-section-header > label > span {
+ padding: 0 10px 0 0;
+ }
+
+ .swagger-ui .opblock .opblock-summary-method {
+ background: #000;
+ color: #fff;
+ text-shadow: rgba(0, 0, 0, .1) 0 1px 0;
+ }
+
+ .swagger-ui .opblock.opblock-post {
+ background: rgba(72, 203, 144, .1);
+ border-color: #48cb90;
+ }
+
+ .swagger-ui .opblock.opblock-post .opblock-summary-method, .swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span::after {
+ background: #48cb90;
+ }
+
+ .swagger-ui .opblock.opblock-post .opblock-summary {
+ border-color: #48cb90;
+ }
+
+ .swagger-ui .opblock.opblock-put {
+ background: rgba(213, 157, 88, .1);
+ border-color: #d59d58;
+ }
+
+ .swagger-ui .opblock.opblock-put .opblock-summary-method, .swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span::after {
+ background: #d59d58;
+ }
+
+ .swagger-ui .opblock.opblock-put .opblock-summary {
+ border-color: #d59d58;
+ }
+
+ .swagger-ui .opblock.opblock-delete {
+ background: rgba(200, 50, 50, .1);
+ border-color: #c83232;
+ }
+
+ .swagger-ui .opblock.opblock-delete .opblock-summary-method, .swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span::after {
+ background: #c83232;
+ }
+
+ .swagger-ui .opblock.opblock-delete .opblock-summary {
+ border-color: #c83232;
+ }
+
+ .swagger-ui .opblock.opblock-get {
+ background: rgba(42, 105, 167, .1);
+ border-color: #2a69a7;
+ }
+
+ .swagger-ui .opblock.opblock-get .opblock-summary-method, .swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span::after {
+ background: #2a69a7;
+ }
+
+ .swagger-ui .opblock.opblock-get .opblock-summary {
+ border-color: #2a69a7;
+ }
+
+ .swagger-ui .opblock.opblock-patch {
+ background: rgba(92, 214, 188, .1);
+ border-color: #5cd6bc;
+ }
+
+ .swagger-ui .opblock.opblock-patch .opblock-summary-method, .swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span::after {
+ background: #5cd6bc;
+ }
+
+ .swagger-ui .opblock.opblock-patch .opblock-summary {
+ border-color: #5cd6bc;
+ }
+
+ .swagger-ui .opblock.opblock-head {
+ background: rgba(140, 63, 207, .1);
+ border-color: #8c3fcf;
+ }
+
+ .swagger-ui .opblock.opblock-head .opblock-summary-method, .swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span::after {
+ background: #8c3fcf;
+ }
+
+ .swagger-ui .opblock.opblock-head .opblock-summary {
+ border-color: #8c3fcf;
+ }
+
+ .swagger-ui .opblock.opblock-options {
+ background: rgba(36, 89, 143, .1);
+ border-color: #24598f;
+ }
+
+ .swagger-ui .opblock.opblock-options .opblock-summary-method, .swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span::after {
+ background: #24598f;
+ }
+
+ .swagger-ui .opblock.opblock-options .opblock-summary {
+ border-color: #24598f;
+ }
+
+ .swagger-ui .opblock.opblock-deprecated {
+ background: rgba(46, 46, 46, .1);
+ border-color: #2e2e2e;
+ opacity: .6;
+ }
+
+ .swagger-ui .opblock.opblock-deprecated .opblock-summary-method, .swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span::after {
+ background: #2e2e2e;
+ }
+
+ .swagger-ui .opblock.opblock-deprecated .opblock-summary {
+ border-color: #2e2e2e;
+ }
+
+.swagger-ui .filter .operation-filter-input {
+ border: 2px solid #2b3446;
+}
+
+.swagger-ui .tab li:first-of-type::after {
+ background: rgba(0, 0, 0, .2);
+}
+
+.swagger-ui .download-contents {
+ background: #7c8192;
+ color: #fff;
+}
+
+.swagger-ui .scheme-container {
+ background: #1c1c21;
+ box-shadow: rgba(0, 0, 0, .15) 0 1px 2px 0;
+}
+
+.swagger-ui .loading-container .loading::before {
+ animation: 1s linear 0s infinite normal none running rotation, .5s ease 0s 1 normal none running opacity;
+ border-color: rgba(0, 0, 0, .6) rgba(84, 84, 84, .1) rgba(84, 84, 84, .1);
+}
+
+.swagger-ui .response-control-media-type--accept-controller select {
+ border-color: #196619;
+}
+
+.swagger-ui .response-control-media-type__accept-message {
+ color: #99e699;
+}
+
+.swagger-ui .version-pragma__message code {
+ background-color: #3b3b3b;
+}
+
+.swagger-ui .btn {
+ background: 0 0;
+ border: 2px solid gray;
+ box-shadow: rgba(0, 0, 0, .1) 0 1px 2px;
+ color: #b5bac9;
+}
+
+ .swagger-ui .btn:hover {
+ box-shadow: rgba(0, 0, 0, .3) 0 0 5px;
+ }
+
+ .swagger-ui .btn.authorize, .swagger-ui .btn.cancel {
+ background-color: transparent;
+ border-color: #a72a2a;
+ color: #e69999;
+ }
+
+ .swagger-ui .btn.authorize {
+ border-color: #48cb90;
+ color: #9ce3c3;
+ }
+
+ .swagger-ui .btn.authorize svg {
+ fill: #9ce3c3;
+ }
+
+ .swagger-ui .btn.execute {
+ background-color: #5892d5;
+ border-color: #5892d5;
+ color: #fff;
+ }
+
+.swagger-ui .copy-to-clipboard {
+ background: #7c8192;
+}
+
+ .swagger-ui .copy-to-clipboard button {
+ background: url("data:image/svg+xml;charset=utf-8,") 50% center no-repeat;
+ }
+
+.swagger-ui select {
+ background: url("data:image/svg+xml;charset=utf-8,") right 10px center/20px no-repeat #212121;
+ background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQuc3ZnIgogICBpZD0ic3ZnNCIKICAgdmVyc2lvbj0iMS4xIgogICB2aWV3Qm94PSIwIDAgMjAgMjAiPgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTEwIj4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZGVmcwogICAgIGlkPSJkZWZzOCIgLz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaW5rc2NhcGU6Y3VycmVudC1sYXllcj0ic3ZnNCIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIxIgogICAgIGlua3NjYXBlOndpbmRvdy15PSItOSIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iLTkiCiAgICAgaW5rc2NhcGU6Y3k9IjEwIgogICAgIGlua3NjYXBlOmN4PSIxMCIKICAgICBpbmtzY2FwZTp6b29tPSI0MS41IgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEzLjQxOCA3Ljg1OWEuNjk1LjY5NSAwIDAxLjk3OCAwIC42OC42OCAwIDAxMCAuOTY5bC0zLjkwOCAzLjgzYS42OTcuNjk3IDAgMDEtLjk3OSAwbC0zLjkwOC0zLjgzYS42OC42OCAwIDAxMC0uOTY5LjY5NS42OTUgMCAwMS45NzggMEwxMCAxMWwzLjQxOC0zLjE0MXoiIC8+Cjwvc3ZnPgo=) right 10px center/20px no-repeat #1c1c21;
+ border: 2px solid #41444e;
+}
+
+ .swagger-ui select[multiple] {
+ background: #212121;
+ }
+
+ .swagger-ui button.invalid, .swagger-ui input[type=email].invalid, .swagger-ui input[type=file].invalid, .swagger-ui input[type=password].invalid, .swagger-ui input[type=search].invalid, .swagger-ui input[type=text].invalid, .swagger-ui select.invalid, .swagger-ui textarea.invalid {
+ background: #390e0e;
+ border-color: #c83232;
+ }
+
+.swagger-ui input[type=email], .swagger-ui input[type=file], .swagger-ui input[type=password], .swagger-ui input[type=search], .swagger-ui input[type=text], .swagger-ui textarea {
+ background: #1c1c21;
+ border: 1px solid #404040;
+}
+
+.swagger-ui textarea {
+ background: rgba(28, 28, 33, .8);
+ color: #b5bac9;
+}
+
+.swagger-ui input[disabled], .swagger-ui select[disabled] {
+ background-color: #1f1f1f;
+ color: #bfbfbf;
+}
+
+.swagger-ui textarea[disabled] {
+ background-color: #41444e;
+ color: #fff;
+}
+
+.swagger-ui select[disabled] {
+ border-color: #878787;
+}
+
+.swagger-ui textarea:focus {
+ border: 2px solid #2a69a7;
+}
+
+.swagger-ui .checkbox input[type=checkbox] + label > .item {
+ background: #303030;
+ box-shadow: #303030 0 0 0 2px;
+}
+
+.swagger-ui .checkbox input[type=checkbox]:checked + label > .item {
+ background: url("data:image/svg+xml;charset=utf-8,") 50% center no-repeat #303030;
+}
+
+.swagger-ui .dialog-ux .backdrop-ux {
+ background: rgba(0, 0, 0, .8);
+}
+
+.swagger-ui .dialog-ux .modal-ux {
+ background: #1c1c21;
+ border: 1px solid #2e2e2e;
+ box-shadow: rgba(0, 0, 0, .2) 0 10px 30px 0;
+}
+
+.swagger-ui .dialog-ux .modal-ux-header .close-modal {
+ background: 0 0;
+}
+
+.swagger-ui .model .deprecated span, .swagger-ui .model .deprecated td {
+ color: #bfbfbf !important;
+}
+
+.swagger-ui .model-toggle::after {
+ background: url("data:image/svg+xml;charset=utf-8,") 50% center/100% no-repeat;
+}
+
+.swagger-ui .model-hint {
+ background: rgba(0, 0, 0, .7);
+ color: #ebebeb;
+}
+
+.swagger-ui section.models {
+ border: 1px solid rgba(58, 64, 80, .3);
+}
+
+ .swagger-ui section.models.is-open h4 {
+ border-bottom: 1px solid rgba(58, 64, 80, .3);
+ }
+
+ .swagger-ui section.models .model-container {
+ background: rgba(0, 0, 0, .05);
+ }
+
+ .swagger-ui section.models .model-container:hover {
+ background: rgba(0, 0, 0, .07);
+ }
+
+.swagger-ui .model-box {
+ background: rgba(0, 0, 0, .1);
+}
+
+.swagger-ui .prop-type {
+ color: #aaaad4;
+}
+
+.swagger-ui table thead tr td, .swagger-ui table thead tr th {
+ border-bottom: 1px solid rgba(58, 64, 80, .2);
+ color: #b5bac9;
+}
+
+.swagger-ui .parameter__name.required::after {
+ color: rgba(230, 153, 153, .6);
+}
+
+.swagger-ui .topbar .download-url-wrapper .select-label {
+ color: #f0f0f0;
+}
+
+.swagger-ui .topbar .download-url-wrapper .download-url-button {
+ background: #63a040;
+ color: #fff;
+}
+
+.swagger-ui .info .title small {
+ background: #7c8492;
+}
+
+ .swagger-ui .info .title small.version-stamp {
+ background-color: #7a9b27;
+ }
+
+.swagger-ui .auth-container .errors {
+ background-color: #350d0d;
+ color: #b5bac9;
+}
+
+.swagger-ui .errors-wrapper {
+ background: rgba(200, 50, 50, .1);
+ border: 2px solid #c83232;
+}
+
+.swagger-ui .markdown code, .swagger-ui .renderedmarkdown code {
+ background: rgba(0, 0, 0, .05);
+ color: #c299e6;
+}
+
+.swagger-ui .model-toggle:after {
+ background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMS4wICg0MDM1YTRmYjQ5LCAyMDIwLTA1LTAxKSIKICAgc29kaXBvZGk6ZG9jbmFtZT0iZG93bmxvYWQyLnN2ZyIKICAgaWQ9InN2ZzQiCiAgIHZlcnNpb249IjEuMSIKICAgaGVpZ2h0PSIyNCIKICAgd2lkdGg9IjI0Ij4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGExMCI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGRlZnMKICAgICBpZD0iZGVmczgiIC8+CiAgPHNvZGlwb2RpOm5hbWVkdmlldwogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9InN2ZzQiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iLTkiCiAgICAgaW5rc2NhcGU6d2luZG93LXg9Ii05IgogICAgIGlua3NjYXBlOmN5PSIxMiIKICAgICBpbmtzY2FwZTpjeD0iMTIiCiAgICAgaW5rc2NhcGU6em9vbT0iMzQuNTgzMzMzIgogICAgIHNob3dncmlkPSJmYWxzZSIKICAgICBpZD0ibmFtZWR2aWV3NiIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDAxIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIgogICAgIGd1aWRldG9sZXJhbmNlPSIxMCIKICAgICBncmlkdG9sZXJhbmNlPSIxMCIKICAgICBvYmplY3R0b2xlcmFuY2U9IjEwIgogICAgIGJvcmRlcm9wYWNpdHk9IjEiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgcGFnZWNvbG9yPSIjZmZmZmZmIiAvPgogIDxwYXRoCiAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZiIKICAgICBpZD0icGF0aDIiCiAgICAgZD0iTTEwIDZMOC41OSA3LjQxIDEzLjE3IDEybC00LjU4IDQuNTlMMTAgMThsNi02eiIgLz4KPC9zdmc+Cg==) 50% no-repeat;
+}
+
+.swagger-ui .expand-operation svg, .swagger-ui section.models h4 svg {
+ fill: #fff;
+}
+
+::-webkit-scrollbar-track {
+ background-color: #646464 !important;
+}
+
+::-webkit-scrollbar-thumb {
+ background-color: #242424 !important;
+ border: 2px solid #3e4346 !important;
+}
+
+::-webkit-scrollbar-button:vertical:start:decrement {
+ background: linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%), linear-gradient(230deg, #696969 40%, transparent 41%), linear-gradient(0deg, #696969 40%, transparent 31%);
+ background-color: #b6b6b6;
+}
+
+::-webkit-scrollbar-button:vertical:end:increment {
+ background: linear-gradient(310deg, #696969 40%, transparent 41%), linear-gradient(50deg, #696969 40%, transparent 41%), linear-gradient(180deg, #696969 40%, transparent 31%);
+ background-color: #b6b6b6;
+}
+
+::-webkit-scrollbar-button:horizontal:end:increment {
+ background: linear-gradient(210deg, #696969 40%, transparent 41%), linear-gradient(330deg, #696969 40%, transparent 41%), linear-gradient(90deg, #696969 30%, transparent 31%);
+ background-color: #b6b6b6;
+}
+
+::-webkit-scrollbar-button:horizontal:start:decrement {
+ background: linear-gradient(30deg, #696969 40%, transparent 41%), linear-gradient(150deg, #696969 40%, transparent 41%), linear-gradient(270deg, #696969 30%, transparent 31%);
+ background-color: #b6b6b6;
+}
+
+::-webkit-scrollbar-button, ::-webkit-scrollbar-track-piece {
+ background-color: #3e4346 !important;
+}
+
+.swagger-ui .black, .swagger-ui .checkbox, .swagger-ui .dark-gray, .swagger-ui .download-url-wrapper .loading, .swagger-ui .errors-wrapper .errors small, .swagger-ui .fallback, .swagger-ui .filter .loading, .swagger-ui .gray, .swagger-ui .hover-black:focus, .swagger-ui .hover-black:hover, .swagger-ui .hover-dark-gray:focus, .swagger-ui .hover-dark-gray:hover, .swagger-ui .hover-gray:focus, .swagger-ui .hover-gray:hover, .swagger-ui .hover-light-silver:focus, .swagger-ui .hover-light-silver:hover, .swagger-ui .hover-mid-gray:focus, .swagger-ui .hover-mid-gray:hover, .swagger-ui .hover-near-black:focus, .swagger-ui .hover-near-black:hover, .swagger-ui .hover-silver:focus, .swagger-ui .hover-silver:hover, .swagger-ui .light-silver, .swagger-ui .markdown pre, .swagger-ui .mid-gray, .swagger-ui .model .property, .swagger-ui .model .property.primitive, .swagger-ui .model-title, .swagger-ui .near-black, .swagger-ui .parameter__extension, .swagger-ui .parameter__in, .swagger-ui .prop-format, .swagger-ui .renderedmarkdown pre, .swagger-ui .response-col_links .response-undocumented, .swagger-ui .response-col_status .response-undocumented, .swagger-ui .silver, .swagger-ui section.models h4, .swagger-ui section.models h5, .swagger-ui span.token-not-formatted, .swagger-ui span.token-string, .swagger-ui table.headers .header-example, .swagger-ui table.model tr.description, .swagger-ui table.model tr.extension {
+ color: #bfbfbf;
+}
+
+.swagger-ui .hover-white:focus, .swagger-ui .hover-white:hover, .swagger-ui .info .title small pre, .swagger-ui .topbar a, .swagger-ui .white {
+ color: #fff;
+}
+
+.swagger-ui .bg-black-10, .swagger-ui .hover-bg-black-10:focus, .swagger-ui .hover-bg-black-10:hover, .swagger-ui .stripe-dark:nth-child(2n + 1) {
+ background-color: rgba(0, 0, 0, .1);
+}
+
+.swagger-ui .bg-white-10, .swagger-ui .hover-bg-white-10:focus, .swagger-ui .hover-bg-white-10:hover, .swagger-ui .stripe-light:nth-child(2n + 1) {
+ background-color: rgba(28, 28, 33, .1);
+}
+
+.swagger-ui .bg-light-silver, .swagger-ui .hover-bg-light-silver:focus, .swagger-ui .hover-bg-light-silver:hover, .swagger-ui .striped--light-silver:nth-child(2n + 1) {
+ background-color: #6e6e6e;
+}
+
+.swagger-ui .bg-moon-gray, .swagger-ui .hover-bg-moon-gray:focus, .swagger-ui .hover-bg-moon-gray:hover, .swagger-ui .striped--moon-gray:nth-child(2n + 1) {
+ background-color: #4d4d4d;
+}
+
+.swagger-ui .bg-light-gray, .swagger-ui .hover-bg-light-gray:focus, .swagger-ui .hover-bg-light-gray:hover, .swagger-ui .striped--light-gray:nth-child(2n + 1) {
+ background-color: #2b2b2b;
+}
+
+.swagger-ui .bg-near-white, .swagger-ui .hover-bg-near-white:focus, .swagger-ui .hover-bg-near-white:hover, .swagger-ui .striped--near-white:nth-child(2n + 1) {
+ background-color: #242424;
+}
+
+.swagger-ui .opblock-tag:hover, .swagger-ui section.models h4:hover {
+ background: rgba(0, 0, 0, .02);
+}
+
+.swagger-ui .checkbox p, .swagger-ui .dialog-ux .modal-ux-content h4, .swagger-ui .dialog-ux .modal-ux-content p, .swagger-ui .dialog-ux .modal-ux-header h3, .swagger-ui .errors-wrapper .errors h4, .swagger-ui .errors-wrapper hgroup h4, .swagger-ui .info .base-url, .swagger-ui .info .title, .swagger-ui .info h1, .swagger-ui .info h2, .swagger-ui .info h3, .swagger-ui .info h4, .swagger-ui .info h5, .swagger-ui .info li, .swagger-ui .info p, .swagger-ui .info table, .swagger-ui .loading-container .loading::after, .swagger-ui .model, .swagger-ui .opblock .opblock-section-header h4, .swagger-ui .opblock .opblock-section-header > label, .swagger-ui .opblock .opblock-summary-description, .swagger-ui .opblock .opblock-summary-operation-id, .swagger-ui .opblock .opblock-summary-path, .swagger-ui .opblock .opblock-summary-path__deprecated, .swagger-ui .opblock-description-wrapper, .swagger-ui .opblock-description-wrapper h4, .swagger-ui .opblock-description-wrapper p, .swagger-ui .opblock-external-docs-wrapper, .swagger-ui .opblock-external-docs-wrapper h4, .swagger-ui .opblock-external-docs-wrapper p, .swagger-ui .opblock-tag small, .swagger-ui .opblock-title_normal, .swagger-ui .opblock-title_normal h4, .swagger-ui .opblock-title_normal p, .swagger-ui .parameter__name, .swagger-ui .parameter__type, .swagger-ui .response-col_links, .swagger-ui .response-col_status, .swagger-ui .responses-inner h4, .swagger-ui .responses-inner h5, .swagger-ui .scheme-container .schemes > label, .swagger-ui .scopes h2, .swagger-ui .servers > label, .swagger-ui .tab li, .swagger-ui label, .swagger-ui select, .swagger-ui table.headers td {
+ color: #b5bac9;
+}
+
+ .swagger-ui .download-url-wrapper .failed, .swagger-ui .filter .failed, .swagger-ui .model-deprecated-warning, .swagger-ui .parameter__deprecated, .swagger-ui .parameter__name.required span, .swagger-ui table.model tr.property-row .star {
+ color: #e69999;
+ }
+
+.swagger-ui .opblock-body pre.microlight, .swagger-ui textarea.curl {
+ background: #41444e;
+ border-radius: 4px;
+ color: #fff;
+}
+
+.swagger-ui .expand-methods svg, .swagger-ui .expand-methods:hover svg {
+ fill: #bfbfbf;
+}
+
+.swagger-ui .auth-container, .swagger-ui .dialog-ux .modal-ux-header {
+ border-bottom: 1px solid #2e2e2e;
+}
+
+.swagger-ui .topbar .download-url-wrapper .select-label select, .swagger-ui .topbar .download-url-wrapper input[type=text] {
+ border: 2px solid #63a040;
+}
+
+.swagger-ui .info a, .swagger-ui .info a:hover, .swagger-ui .scopes h2 a {
+ color: #99bde6;
+}
+
+/* Dark Scrollbar */
+::-webkit-scrollbar {
+ width: 14px;
+ height: 14px;
+}
+
+::-webkit-scrollbar-button {
+ background-color: #3e4346 !important;
+}
+
+::-webkit-scrollbar-track {
+ background-color: #646464 !important;
+}
+
+::-webkit-scrollbar-track-piece {
+ background-color: #3e4346 !important;
+}
+
+::-webkit-scrollbar-thumb {
+ height: 50px;
+ background-color: #242424 !important;
+ border: 2px solid #3e4346 !important;
+}
+
+::-webkit-scrollbar-corner {
+}
+
+::-webkit-resizer {
+}
+
+::-webkit-scrollbar-button:vertical:start:decrement {
+ background: linear-gradient(130deg, #696969 40%, rgba(255, 0, 0, 0) 41%), linear-gradient(230deg, #696969 40%, rgba(0, 0, 0, 0) 41%), linear-gradient(0deg, #696969 40%, rgba(0, 0, 0, 0) 31%);
+ background-color: #b6b6b6;
+}
+
+::-webkit-scrollbar-button:vertical:end:increment {
+ background: linear-gradient(310deg, #696969 40%, rgba(0, 0, 0, 0) 41%), linear-gradient(50deg, #696969 40%, rgba(0, 0, 0, 0) 41%), linear-gradient(180deg, #696969 40%, rgba(0, 0, 0, 0) 31%);
+ background-color: #b6b6b6;
+}
+
+::-webkit-scrollbar-button:horizontal:end:increment {
+ background: linear-gradient(210deg, #696969 40%, rgba(0, 0, 0, 0) 41%), linear-gradient(330deg, #696969 40%, rgba(0, 0, 0, 0) 41%), linear-gradient(90deg, #696969 30%, rgba(0, 0, 0, 0) 31%);
+ background-color: #b6b6b6;
+}
+
+::-webkit-scrollbar-button:horizontal:start:decrement {
+ background: linear-gradient(30deg, #696969 40%, rgba(0, 0, 0, 0) 41%), linear-gradient(150deg, #696969 40%, rgba(0, 0, 0, 0) 41%), linear-gradient(270deg, #696969 30%, rgba(0, 0, 0, 0) 31%);
+ background-color: #b6b6b6;
+}
diff --git a/sample/SampleAppJson/ApiDefinition.json b/sample/SampleAppJson/ApiDefinition.json
new file mode 100644
index 0000000..dc4f842
--- /dev/null
+++ b/sample/SampleAppJson/ApiDefinition.json
@@ -0,0 +1,18 @@
+[
+ {
+ "name": "Car",
+ "route": "/cars",
+ "caching": true,
+ "cacheLiveTime": 1000,
+ "events": "POST,PUT,DELETE",
+ "idType": "int",
+ "Fields": [
+ {
+ "name": "Name",
+ "type": "String",
+ "maxLength": "200",
+ "nullable": false
+ }
+ ]
+ }
+ ]
diff --git a/sample/SampleAppJson/Program.cs b/sample/SampleAppJson/Program.cs
new file mode 100644
index 0000000..501788d
--- /dev/null
+++ b/sample/SampleAppJson/Program.cs
@@ -0,0 +1,24 @@
+using System.Configuration;
+using System.Reflection;
+using Newtonsoft.Json;
+using TCDev.ApiGenerator.Extension;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.Services.AddControllers();
+
+builder.Services.AddApiGeneratorServices(builder.Configuration, Assembly.GetExecutingAssembly());
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+app.UseAutomaticApiMigrations();
+app.UseHttpsRedirection();
+
+app.UseAuthorization();
+app.UseApiGenerator();
+app.MapControllers();
+
+app.Run();
diff --git a/sample/SampleAppJson/Properties/launchSettings.json b/sample/SampleAppJson/Properties/launchSettings.json
new file mode 100644
index 0000000..29f040a
--- /dev/null
+++ b/sample/SampleAppJson/Properties/launchSettings.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:25638",
+ "sslPort": 44337
+ }
+ },
+ "profiles": {
+ "SampleAppNuget": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": true,
+ "launchUrl": "weatherforecast",
+ "applicationUrl": "https://localhost:7114;http://localhost:5114",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "weatherforecast",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/sample/SampleAppJson/SampleAppJson.csproj b/sample/SampleAppJson/SampleAppJson.csproj
new file mode 100644
index 0000000..155556e
--- /dev/null
+++ b/sample/SampleAppJson/SampleAppJson.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net6.0
+ enable
+ enable
+ Debug;Release;DebugWithSampleApp;SampleAppNuget;SampleAppJson
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/SampleAppJson/appsettings.Development.json b/sample/SampleAppJson/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/sample/SampleAppJson/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/sample/SampleAppJson/appsettings.json b/sample/SampleAppJson/appsettings.json
new file mode 100644
index 0000000..ab2ebdd
--- /dev/null
+++ b/sample/SampleAppJson/appsettings.json
@@ -0,0 +1,34 @@
+{
+ "ConnectionStrings": {
+ "ApiGeneratorDatabase": "Server=localhost;database=tcdev_dev_222;user=sa;password=Password!23;"
+ },
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AllowedHosts": "*",
+
+ "Api": {
+ "Swagger": {
+ "EnableProduction": "false", // Enable/Disable for production builds
+ "Description": "Sample Swagger Config",
+ "Version": "v1",
+ "Title": "Sample Swagger Config Title",
+ "ContactMail": "Me@me.de",
+ "ContactUri": "https://www.myuri.com"
+ },
+ "Database": {
+ "DatabaseType": "InMemory",
+ "Connection": ""
+ },
+ "Odata": {
+ "Enabled": true,
+ "EnableSelect": true,
+ "EnableFilter": true,
+ "EnableSort": true
+ }
+ }
+}
diff --git a/sample/SampleAppNuget/SampleAPis/Person.cs b/sample/SampleAppNuget/SampleAPis/Person.cs
index 69895c7..a2f57b0 100644
--- a/sample/SampleAppNuget/SampleAPis/Person.cs
+++ b/sample/SampleAppNuget/SampleAPis/Person.cs
@@ -28,12 +28,6 @@ public class Person : Trackable,
public Guid Id { get; set; }
- public void Configure(EntityTypeBuilder builder)
- {
- builder.ToTable("MyFancyTableName");
- //....all the other EF Core Options
- }
-
///
/// Before Delete Hook
///
@@ -58,6 +52,10 @@ public Task BeforeUpdate(Person newPerson, Person oldPerson)
return Task.FromResult(newPerson);
}
+ public void Configure(EntityTypeBuilder builder)
+ {
+
+ }
}
}
\ No newline at end of file
diff --git a/sample/SampleAppNuget/SampleAppNuget.csproj b/sample/SampleAppNuget/SampleAppNuget.csproj
index 67ff66c..e034d84 100644
--- a/sample/SampleAppNuget/SampleAppNuget.csproj
+++ b/sample/SampleAppNuget/SampleAppNuget.csproj
@@ -1,10 +1,10 @@
-
+
net6.0
enable
enable
- Debug;Release;DebugWithSampleApp;SampleAppNuget
+ Debug;Release;DebugWithSampleApp;SampleAppNuget;SampleAppJson
@@ -13,7 +13,7 @@
-
+
diff --git a/sample/SampleAppNuget/appsettings.json b/sample/SampleAppNuget/appsettings.json
index 5d67d73..ab2ebdd 100644
--- a/sample/SampleAppNuget/appsettings.json
+++ b/sample/SampleAppNuget/appsettings.json
@@ -25,6 +25,7 @@
"Connection": ""
},
"Odata": {
+ "Enabled": true,
"EnableSelect": true,
"EnableFilter": true,
"EnableSort": true
diff --git a/sample/SampleClasses/Car.cs b/sample/SampleClasses/Car.cs
deleted file mode 100644
index 7492d2f..0000000
--- a/sample/SampleClasses/Car.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using EntityFramework.Triggers;
-using Maximago.ApiGenerator.Attributes;
-using Maximago.ApiGenerator.Interfaces;
-using Maximago.ApiGenerator.Schemes;
-using Maximago.ApiGenerator.Schemes.Interfaces;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Metadata.Builders;
-using Newtonsoft.Json;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace ApiGeneratorSampleApp
-{
-
- [GeneratedController(
- route: "/cars/",
- requiredReadClaims: new string[] { "car.read" },
- requiredWriteClaims: new string[] { "car.write" },
- fireEvents: true,
- cacheDuration: 10000,
- cache: true
- )]
- public class Car :
- Trackable,
- IObjectBase,
- IEntityTypeConfiguration,
- IHasQueryFields
- {
-
- [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- [Key]
- public int Id { get; set; }
-
- [Required]
- public string Name { get; set; }
-
- [Required]
- public string Brand { get; set; }
-
- [Required]
- public string Make { get; set; }
-
- public CarDealer? Dealer { get; set; }
-
- ///
- /// All Fields of the class that are querried by q param on controller
- ///
- public string[] QueryFields { get { return new string[] { "Name", "Brand", "Make" }; } }
-
- public void Configure(EntityTypeBuilder builder)
- {
- builder.HasKey("Id");
- builder.ToTable("car");
- builder.HasOne("Dealer").WithMany("Cars");
-
- }
- }
-}
diff --git a/sample/SampleClasses/CarDealer.cs b/sample/SampleClasses/CarDealer.cs
deleted file mode 100644
index 76b0773..0000000
--- a/sample/SampleClasses/CarDealer.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Maximago.ApiGenerator.Attributes;
-using Maximago.ApiGenerator.Interfaces;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Metadata.Builders;
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-
-namespace ApiGeneratorSampleApp
-{
- [GeneratedController(
- route: "/cardealer",
- authorize: false
- )]
- public class CarDealer : IObjectBase, IEntityTypeConfiguration
- {
- [Key]
- public string Id { get; set; } = System.Guid.NewGuid().ToString();
-
- public virtual List? Cars { get; set; } = new List();
-
-
- public void Configure(EntityTypeBuilder builder)
- {
- builder.HasKey(p => p.Id);
-
- builder.HasMany(p => p.Cars).WithOne(p => p.Dealer);
- }
-
- }
-
-
-}
diff --git a/sample/SampleClasses/Person.cs b/sample/SampleClasses/Person.cs
deleted file mode 100644
index ba38631..0000000
--- a/sample/SampleClasses/Person.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using GraphQL.Types;
-using Maximago.ApiGenerator.Attributes;
-using Maximago.ApiGenerator.Interfaces;
-using Maximago.ApiGenerator.Schemes.Interfaces;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Metadata.Builders;
-using System;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace ApiGeneratorSampleApI.Model
-{
-
- [Api( route: "/people2" )]
- public class PersonAutoMode : IObjectBase
- {
-
- public Guid Id { get; set; }
-
- public string Name { get; set; }
-
- public string Description { get; set; }
-
- }
-
-
- [Api(
- route: "/people",
- authorize: false
- )]
- public class Person :
- IObjectBase,
- IEntityTypeConfiguration
- {
- [Key]
- [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public Guid Id { get; set; }
-
- public string Name { get; set; }
-
- public string Description { get; set; }
-
- public void Configure(EntityTypeBuilder builder)
- {
- //default stuff if nothing special
- }
- }
-}
diff --git a/sample/SampleClasses/SampleClasses.csproj b/sample/SampleClasses/SampleClasses.csproj
deleted file mode 100644
index 2494ef0..0000000
--- a/sample/SampleClasses/SampleClasses.csproj
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
- net6.0
- enable
- enable
- False
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/sample/SampleClasses/Test.cs b/sample/SampleClasses/Test.cs
deleted file mode 100644
index 5b4ae86..0000000
--- a/sample/SampleClasses/Test.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Maximago.ApiGenerator.Attributes;
-using Maximago.ApiGenerator.Interfaces;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Metadata.Builders;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-
-namespace ApiGeneratorSampleApI.Model
-{
-
- [GeneratedController(
- route: "/test/",
- requiredReadClaims: new string[] { "test.read" },
- requiredWriteClaims: new string[] { "test.write" },
- fireEvents: true,
- cacheDuration: 10000,
- cache: true
- )]
- public class Test :
- IObjectBase,
- IEntityTypeConfiguration,
- IHasQueryFields
- {
- public string Id { get; set; }
-
- public string[] QueryFields => throw new System.NotImplementedException();
-
- public void Configure(EntityTypeBuilder builder)
- {
- builder.HasKey("Id");
- }
- }
-}
diff --git a/src/TCDev.APIGenerator.Caching/TCDev.APIGenerator.Caching.csproj b/src/TCDev.APIGenerator.Caching/TCDev.APIGenerator.Caching.csproj
index e2b847e..bc4994e 100644
--- a/src/TCDev.APIGenerator.Caching/TCDev.APIGenerator.Caching.csproj
+++ b/src/TCDev.APIGenerator.Caching/TCDev.APIGenerator.Caching.csproj
@@ -2,7 +2,7 @@
net6.0
- Debug;Release;DebugWithSampleApp;SampleAppNuget
+ Debug;Release;DebugWithSampleApp;SampleAppNuget;SampleAppJson
diff --git a/src/TCDev.APIGenerator.DbFirst/Generator.cs b/src/TCDev.APIGenerator.DbFirst/Generator.cs
new file mode 100644
index 0000000..34c527b
--- /dev/null
+++ b/src/TCDev.APIGenerator.DbFirst/Generator.cs
@@ -0,0 +1,106 @@
+// TCDev.de 2022/04/05
+// TCDev.APIGenerator.Generator.cs
+// https://www.github.com/deejaytc/dotnet-utils
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Newtonsoft.Json;
+using TCDev.APIGenerator.Schema;
+
+
+namespace TCDev.ApiGenerator.Json;
+
+public class JsonClassBuilder
+{
+
+ public static Type CreateClass(JsonClassDefinition definition)
+ {
+ try
+ {
+ var classCode = $@" // Auto-generated code
+ using System;
+ using Swashbuckle.AspNetCore.Annotations;
+ using System.ComponentModel.DataAnnotations;
+ using System.ComponentModel.DataAnnotations.Schema;
+ using System.Text.Json.Serialization;
+ using TCDev.ApiGenerator.Attributes;
+ using TCDev.ApiGenerator.Interfaces;
+
+ namespace TCDev.ApiGenerator
+ {{
+ [Api(""{ definition.RouteTemplate }"")]
+ public class { definition.Name } : IObjectBase<{definition.IdType}>
+
+ // Add Properties
+ {{
+ public {definition.IdType} Id {{ get; set;}}
+ ";
+
+ // Add all fields
+ var result1 = definition.Fields.Aggregate(string.Empty, (current, field) =>
+ current + $@" public {field.Type} {field.Name}{(field.Nullable ? "?" : "")} {{ get; set;}}");
+
+ // Complete class
+ classCode += result1;
+ classCode += $@"}} }}";
+
+ MetadataReference[] assemblies = AppDomain
+ .CurrentDomain
+ .GetAssemblies()
+ .Where(a => !string.IsNullOrEmpty(a.Location))
+ .Select(a => MetadataReference.CreateFromFile(a.Location))
+ .ToArray();
+ classCode = FormatUsingRoslyn(classCode);
+
+ var syntaxTree = CSharpSyntaxTree.ParseText(classCode);
+
+ var compilation = CSharpCompilation
+ .Create("TCDev.ApiGenerator")
+ .AddSyntaxTrees(syntaxTree)
+ .AddReferences(assemblies)
+ .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+ using var ms = new MemoryStream();
+ var result = compilation.Emit(ms);
+
+ if (result.Success)
+ {
+ ms.Seek(0, SeekOrigin.Begin);
+ var assembly = Assembly.Load(ms.ToArray());
+
+ var newTypeFullName = $"TCDev.ApiGenerator.{definition.Name}";
+
+ var type = assembly.GetType(newTypeFullName);
+ return type;
+ }
+
+ var failures = result.Diagnostics.Where(diagnostic =>
+ diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);
+
+ foreach (var diagnostic in failures) Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
+
+ return null;
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ throw;
+ }
+ }
+
+ public static string FormatUsingRoslyn(string csCode)
+ {
+ var tree = CSharpSyntaxTree.ParseText(csCode);
+ var root = tree.GetRoot()
+ .NormalizeWhitespace();
+ var result = root.ToFullString();
+ return result;
+ }
+
+}
diff --git a/src/TCDev.APIGenerator.DbFirst/Sample.json b/src/TCDev.APIGenerator.DbFirst/Sample.json
new file mode 100644
index 0000000..b2d504e
--- /dev/null
+++ b/src/TCDev.APIGenerator.DbFirst/Sample.json
@@ -0,0 +1,22 @@
+[
+ {
+ "name": "Car",
+ "route": "/cars",
+ "caching": true,
+ "cacheLiveTime": 1000,
+ "events": "POST,PUT,DELETE",
+ "idType": "int",
+ "Fields": [
+ {
+ "name": "Name",
+ "type": "String",
+ "maxLength": "200"
+ },
+ {
+ "name": "Description",
+ "type": "String",
+ "nullable": true
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/src/TCDev.APIGenerator.DbFirst/TCDev.APIGenerator.Json.csproj b/src/TCDev.APIGenerator.DbFirst/TCDev.APIGenerator.Json.csproj
new file mode 100644
index 0000000..2922adf
--- /dev/null
+++ b/src/TCDev.APIGenerator.DbFirst/TCDev.APIGenerator.Json.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net6.0
+ Debug;Release;DebugWithSampleApp;SampleAppNuget;SampleAppJson
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
diff --git a/src/TCDev.APIGenerator.GraphQL/Attributes/GraphQLAttribute.cs b/src/TCDev.APIGenerator.GraphQL/Attributes/GraphQLAttribute.cs
index 493abdb..8208542 100644
--- a/src/TCDev.APIGenerator.GraphQL/Attributes/GraphQLAttribute.cs
+++ b/src/TCDev.APIGenerator.GraphQL/Attributes/GraphQLAttribute.cs
@@ -1,34 +1,33 @@
-// TCDev 2022/03/16
-// Apache 2.0 License
+// TCDev.de 2022/03/16
+// TCDev.APIGenerator.GraphQL.GraphQLAttribute.cs
// https://www.github.com/deejaytc/dotnet-utils
using System;
-namespace TCDev.ApiGenerator.Attributes
+namespace TCDev.ApiGenerator.Attributes;
+
+[AttributeUsage(AttributeTargets.Class)]
+public class GraphQlAttribute : Attribute
{
- [AttributeUsage(AttributeTargets.Class)]
- public class GraphQLAttribute : Attribute
+ public ApiAttributeAttributeOptions Options { get; set; }
+
+ ///
+ /// Attribute defining auto generated controller for the class
+ ///
+ /// The full base route for the class ie /myclass/
+ ///
+ ///
+ ///
+ ///
+ public GraphQlAttribute(
+ bool fireEvents = false,
+ bool authorize = true,
+ bool cache = false,
+ int cacheDuration = 50000)
{
- ///
- /// Attribute defining auto generated controller for the class
- ///
- /// The full base route for the class ie /myclass/
- ///
- ///
- ///
- ///
- public GraphQLAttribute(
- bool fireEvents = false,
- bool authorize = true,
- bool cache = false,
- int cacheDuration = 50000)
+ this.Options = new ApiAttributeAttributeOptions
{
- Options = new ApiAttributeAttributeOptions
- {
- Authorize = authorize, Cache = cache, CacheDuration = cacheDuration, FireEvents = fireEvents
- };
- }
-
- public ApiAttributeAttributeOptions Options { get; set; }
+ Authorize = authorize, Cache = cache, CacheDuration = cacheDuration, FireEvents = fireEvents
+ };
}
-}
\ No newline at end of file
+}
diff --git a/src/TCDev.APIGenerator.GraphQL/Extension/ApiGeneratorExtension.cs b/src/TCDev.APIGenerator.GraphQL/Extension/ApiGeneratorExtension.cs
index 9c031c6..2c40f92 100644
--- a/src/TCDev.APIGenerator.GraphQL/Extension/ApiGeneratorExtension.cs
+++ b/src/TCDev.APIGenerator.GraphQL/Extension/ApiGeneratorExtension.cs
@@ -1,5 +1,5 @@
-// TCDev 2022/03/16
-// Apache 2.0 License
+// TCDev.de 2022/03/16
+// TCDev.APIGenerator.GraphQL.ApiGeneratorExtension.cs
// https://www.github.com/deejaytc/dotnet-utils
using System.Reflection;
@@ -9,28 +9,27 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-namespace TCDev.ApiGenerator.Extension
+namespace TCDev.ApiGenerator.Extension;
+
+public static class ApiGeneratorExtension
{
- public static class ApiGeneratorExtension
+ public static IServiceCollection AddApiGeneratorGraphQl(this IServiceCollection services, IConfiguration config, Assembly assembly)
{
- public static IServiceCollection AddApiGeneratorGraphQL(this IServiceCollection services, IConfiguration config, Assembly assembly)
- {
- services.Configure(options => { options.AllowSynchronousIO = true; });
+ services.Configure(options => { options.AllowSynchronousIO = true; });
- return services;
- }
+ return services;
+ }
- public static IApplicationBuilder UseAPIGeneratorGraphQL(this IApplicationBuilder app)
- {
- return app;
- }
+ public static IApplicationBuilder UseApiGeneratorGraphQl(this IApplicationBuilder app)
+ {
+ return app;
+ }
- public static IEndpointRouteBuilder UseGraphQLEndpoint(this IEndpointRouteBuilder endpoints)
- {
- endpoints.MapGraphQL();
- return endpoints;
- }
+ public static IEndpointRouteBuilder UseGraphQlEndpoint(this IEndpointRouteBuilder endpoints)
+ {
+ endpoints.MapGraphQL();
+ return endpoints;
}
-}
\ No newline at end of file
+}
diff --git a/src/TCDev.APIGenerator.GraphQL/Schema/GenericGraphQLSchema.cs b/src/TCDev.APIGenerator.GraphQL/Schema/GenericGraphQLSchema.cs
index a3d8079..326a501 100644
--- a/src/TCDev.APIGenerator.GraphQL/Schema/GenericGraphQLSchema.cs
+++ b/src/TCDev.APIGenerator.GraphQL/Schema/GenericGraphQLSchema.cs
@@ -1,20 +1,23 @@
-// TCDev 2022/03/16
-// Apache 2.0 License
+// TCDev.de 2022/03/16
+// TCDev.APIGenerator.GraphQL.GenericGraphQLSchema.cs
// https://www.github.com/deejaytc/dotnet-utils
using System;
using System.Reflection;
-namespace TCDev.ApiGenerator.GraphQL
+namespace TCDev.ApiGenerator.GraphQL;
+
+public class GenericGraphQlSchema
{
- public class GenericGraphQLSchema
+ public GenericGraphQlSchema(IServiceProvider sp)
{
- public GenericGraphQLSchema(IServiceProvider sp)
+ string[] assemblies =
+ {
+ Assembly.GetEntryAssembly()
+ .FullName
+ };
+ foreach (var assembly in assemblies)
{
- string[] assemblies = {Assembly.GetEntryAssembly().FullName};
- foreach (var assembly in assemblies)
- {
- }
}
}
-}
\ No newline at end of file
+}
diff --git a/src/TCDev.APIGenerator.GraphQL/TCDev.APIGenerator.GraphQL.csproj b/src/TCDev.APIGenerator.GraphQL/TCDev.APIGenerator.GraphQL.csproj
index 76d870e..6ea8c95 100644
--- a/src/TCDev.APIGenerator.GraphQL/TCDev.APIGenerator.GraphQL.csproj
+++ b/src/TCDev.APIGenerator.GraphQL/TCDev.APIGenerator.GraphQL.csproj
@@ -2,7 +2,7 @@
net6.0
- Debug;Release;DebugWithSampleApp;SampleAppNuget
+ Debug;Release;DebugWithSampleApp;SampleAppNuget;SampleAppJson
diff --git a/src/TCDev.APIGenerator.Identity/ServiceExtension.cs b/src/TCDev.APIGenerator.Identity/ServiceExtension.cs
new file mode 100644
index 0000000..b898d2e
--- /dev/null
+++ b/src/TCDev.APIGenerator.Identity/ServiceExtension.cs
@@ -0,0 +1,41 @@
+using System.Security.Claims;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.IdentityModel.Tokens;
+
+namespace TCDev.APIGenerator.Identity
+{
+ public static class ServiceExtension
+ {
+ public static IServiceCollection ConfigureIdentity(this IServiceCollection services, IConfiguration configuration)
+ {
+ string domain = $"https://{configuration["Auth0:Domain"]}/";
+ services
+ .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
+ .AddJwtBearer(options =>
+ {
+ options.Authority = domain;
+ options.Audience = configuration["Auth0:Audience"];
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ NameClaimType = ClaimTypes.NameIdentifier
+ };
+ });
+
+
+ return services;
+ }
+
+ public static IApplicationBuilder UseApiGeneratorAuthentication(this IApplicationBuilder app)
+ {
+ app.UseAuthentication();
+ app.UseAuthorization();
+
+ return app;
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/TCDev.APIGenerator.Identity/TCDev.APIGenerator.Identity.csproj b/src/TCDev.APIGenerator.Identity/TCDev.APIGenerator.Identity.csproj
new file mode 100644
index 0000000..c121e0a
--- /dev/null
+++ b/src/TCDev.APIGenerator.Identity/TCDev.APIGenerator.Identity.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/src/TCDev.APIGenerator.Schema/Attributes/SwaggerIgnoreAttribute.cs b/src/TCDev.APIGenerator.Schema/Attributes/SwaggerIgnoreAttribute.cs
new file mode 100644
index 0000000..e1e0159
--- /dev/null
+++ b/src/TCDev.APIGenerator.Schema/Attributes/SwaggerIgnoreAttribute.cs
@@ -0,0 +1,18 @@
+// TCDev 2022/03/16
+// Apache 2.0 License
+// https://www.github.com/deejaytc/dotnet-utils
+
+using System;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+
+namespace TCDev.ApiGenerator.Attributes
+{
+ [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Enum)]
+ public class SwaggerIgnoreAttribute : Attribute
+ {
+ public SwaggerIgnoreAttribute()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/TCDev.APIGenerator.Schema/Interfaces/IAfterCreate.cs b/src/TCDev.APIGenerator.Schema/Interfaces/Hooks/IAfterCreate.cs
similarity index 100%
rename from src/TCDev.APIGenerator.Schema/Interfaces/IAfterCreate.cs
rename to src/TCDev.APIGenerator.Schema/Interfaces/Hooks/IAfterCreate.cs
diff --git a/src/TCDev.APIGenerator.Schema/Interfaces/IAfterDelete.cs b/src/TCDev.APIGenerator.Schema/Interfaces/Hooks/IAfterDelete.cs
similarity index 100%
rename from src/TCDev.APIGenerator.Schema/Interfaces/IAfterDelete.cs
rename to src/TCDev.APIGenerator.Schema/Interfaces/Hooks/IAfterDelete.cs
diff --git a/src/TCDev.APIGenerator.Schema/Interfaces/IAfterUpdate.cs b/src/TCDev.APIGenerator.Schema/Interfaces/Hooks/IAfterUpdate.cs
similarity index 100%
rename from src/TCDev.APIGenerator.Schema/Interfaces/IAfterUpdate.cs
rename to src/TCDev.APIGenerator.Schema/Interfaces/Hooks/IAfterUpdate.cs
diff --git a/src/TCDev.APIGenerator.Schema/Interfaces/IBeforeCreate.cs b/src/TCDev.APIGenerator.Schema/Interfaces/Hooks/IBeforeCreate.cs
similarity index 100%
rename from src/TCDev.APIGenerator.Schema/Interfaces/IBeforeCreate.cs
rename to src/TCDev.APIGenerator.Schema/Interfaces/Hooks/IBeforeCreate.cs
diff --git a/src/TCDev.APIGenerator.Schema/Interfaces/IBeforeDelete.cs b/src/TCDev.APIGenerator.Schema/Interfaces/Hooks/IBeforeDelete.cs
similarity index 100%
rename from src/TCDev.APIGenerator.Schema/Interfaces/IBeforeDelete.cs
rename to src/TCDev.APIGenerator.Schema/Interfaces/Hooks/IBeforeDelete.cs
diff --git a/src/TCDev.APIGenerator.Schema/Interfaces/IBeforeUpdate.cs b/src/TCDev.APIGenerator.Schema/Interfaces/Hooks/IBeforeUpdate.cs
similarity index 100%
rename from src/TCDev.APIGenerator.Schema/Interfaces/IBeforeUpdate.cs
rename to src/TCDev.APIGenerator.Schema/Interfaces/Hooks/IBeforeUpdate.cs
diff --git a/src/TCDev.APIGenerator.Schema/Interfaces/IObjectBase.cs b/src/TCDev.APIGenerator.Schema/Interfaces/IObjectBase.cs
index 3623713..5f2f71d 100644
--- a/src/TCDev.APIGenerator.Schema/Interfaces/IObjectBase.cs
+++ b/src/TCDev.APIGenerator.Schema/Interfaces/IObjectBase.cs
@@ -2,15 +2,16 @@
// Apache 2.0 License
// https://www.github.com/deejaytc/dotnet-utils
+using Swashbuckle.AspNetCore.Annotations;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using TCDev.ApiGenerator.Attributes;
namespace TCDev.ApiGenerator.Interfaces
{
public interface IObjectBase
{
[Key]
- [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
TId Id { get; set; }
}
}
\ No newline at end of file
diff --git a/src/TCDev.APIGenerator.Schema/JsonClassDefinition.cs b/src/TCDev.APIGenerator.Schema/JsonClassDefinition.cs
new file mode 100644
index 0000000..65a855c
--- /dev/null
+++ b/src/TCDev.APIGenerator.Schema/JsonClassDefinition.cs
@@ -0,0 +1,71 @@
+// TCDev.de 2022/04/07
+// TCDev.APIGenerator.Schema.JsonClassDefinition.cs
+// https://github.com/DeeJayTC/net-dynamic-api
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace TCDev.APIGenerator.Schema
+{
+ [Flags]
+ public enum Events
+ {
+ POST,
+ PUT,
+ DELETE,
+ ALL = POST | PUT | DELETE
+ }
+
+
+ public class JsonClassDefinition
+ {
+ public string Name { get; set; }
+
+ [JsonProperty("route")]
+ public string RouteTemplate { get; set; } = "/";
+
+ [JsonProperty("caching")]
+ public bool EnableCaching { get; set; }
+
+ [JsonProperty("idType")]
+ public string IdType { get; set; } = "int";
+
+ public bool Authorize { get; set; } = false;
+
+ [JsonProperty("ScopesRead")]
+ public List ScopesReadList { get; set; } = new List();
+
+ [JsonProperty("ScopesWrite")]
+ public List ScopesWriteList { get; set; } = new List();
+
+
+ [JsonIgnore]
+ public string ScopesRead {
+ get
+ {
+ return ScopesReadList.Any() ? string.Join(",", ScopesReadList.Select(p => $"\"{p}\"").ToList()) : string.Empty;
+ }
+ }
+ [JsonIgnore]
+ public string ScopesWrite
+ {
+ get
+ {
+ return ScopesWriteList.Any() ? string.Join(",", ScopesWriteList.Select(p => $"\"{p}\"").ToList()) : string.Empty;
+ }
+ }
+
+ public List Fields { get; set; }
+ }
+
+
+ public class Field
+ {
+ public string Name { get; set; }
+ public string Type { get; set; }
+ public bool Nullable { get; set; }
+ public string MaxLength { get; set; }
+ }
+}
diff --git a/src/TCDev.APIGenerator.Schema/Schema/Draftable.cs b/src/TCDev.APIGenerator.Schema/Schema/Draftable.cs
deleted file mode 100644
index 7006d48..0000000
--- a/src/TCDev.APIGenerator.Schema/Schema/Draftable.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-// TCDev 2022/03/16
-// Apache 2.0 License
-// https://www.github.com/deejaytc/dotnet-utils
-
-using System;
-using EntityFramework.Triggers;
-
-namespace TCDev.ApiGenerator.Schemes.Interfaces
-{
- public abstract class Trackable
- {
- static Trackable()
- {
- Triggers.Inserting += entry => entry.Entity.Inserted = DateTime.UtcNow;
- Triggers.Updating += entry => entry.Entity.Updated = DateTime.UtcNow;
- }
-
- public virtual DateTime Inserted { get; private set; } = DateTime.UtcNow;
- public virtual DateTime? Updated { get; private set; }
- }
-
- public abstract class SoftDeletable : Trackable
- {
- static SoftDeletable()
- {
- Triggers.Deleting += entry =>
- {
- entry.Entity.SoftDelete();
- entry.Cancel = true; // Cancels the deletion, but will persist changes with the same effects as EntityState.Modified
- };
- }
-
- public virtual DateTime? DeletedAt { get; private set; }
-
- public bool IsSoftDeleted => DeletedAt != null;
-
- public void SoftDelete()
- {
- DeletedAt = DateTime.UtcNow;
- }
-
- public void SoftRestore()
- {
- DeletedAt = null;
- }
- }
-}
\ No newline at end of file
diff --git a/src/TCDev.APIGenerator.Schema/Schema/SoftDeletable.cs b/src/TCDev.APIGenerator.Schema/Schema/SoftDeletable.cs
new file mode 100644
index 0000000..58a2f0c
--- /dev/null
+++ b/src/TCDev.APIGenerator.Schema/Schema/SoftDeletable.cs
@@ -0,0 +1,35 @@
+// TCDev.de 2022/04/07
+// TCDev.APIGenerator.Schema.SoftDeletable.cs
+// https://github.com/DeeJayTC/net-dynamic-api
+
+using System;
+using EntityFramework.Triggers;
+
+namespace TCDev.ApiGenerator.Schemes.Interface
+{
+ public abstract class SoftDeletable : Trackable
+ {
+ public virtual DateTime? DeletedAt { get; private set; }
+
+ public bool IsSoftDeleted => this.DeletedAt != null;
+
+ public void SoftDelete()
+ {
+ this.DeletedAt = DateTime.UtcNow;
+ }
+
+ public void SoftRestore()
+ {
+ this.DeletedAt = null;
+ }
+
+ static SoftDeletable()
+ {
+ Triggers.Deleting += entry =>
+ {
+ entry.Entity.SoftDelete();
+ entry.Cancel = true; // Cancels the deletion, but will persist changes with the same effects as EntityState.Modified
+ };
+ }
+ }
+}
diff --git a/src/TCDev.APIGenerator.Schema/Schema/Trackable.cs b/src/TCDev.APIGenerator.Schema/Schema/Trackable.cs
new file mode 100644
index 0000000..61b0140
--- /dev/null
+++ b/src/TCDev.APIGenerator.Schema/Schema/Trackable.cs
@@ -0,0 +1,21 @@
+// TCDev.de 2022/03/16
+// TCDev.APIGenerator.Schema.Trackable123.cs
+// https://github.com/DeeJayTC/net-dynamic-api
+
+using System;
+using EntityFramework.Triggers;
+
+namespace TCDev.ApiGenerator.Schemes
+{
+ public abstract class Trackable
+ {
+ public virtual DateTime Inserted { get; private set; } = DateTime.UtcNow;
+ public virtual DateTime? Updated { get; private set; }
+
+ static Trackable()
+ {
+ Triggers.Inserting += entry => entry.Entity.Inserted = DateTime.UtcNow;
+ Triggers.Updating += entry => entry.Entity.Updated = DateTime.UtcNow;
+ }
+ }
+}
diff --git a/src/TCDev.APIGenerator.Schema/TCDev.APIGenerator.Schema.csproj b/src/TCDev.APIGenerator.Schema/TCDev.APIGenerator.Schema.csproj
index 00b4e92..ad03681 100644
--- a/src/TCDev.APIGenerator.Schema/TCDev.APIGenerator.Schema.csproj
+++ b/src/TCDev.APIGenerator.Schema/TCDev.APIGenerator.Schema.csproj
@@ -2,11 +2,13 @@
net6.0
- Debug;Release;DebugWithSampleApp;SampleAppNuget
+ Debug;Release;DebugWithSampleApp;SampleAppNuget;SampleAppJson
+
+
diff --git a/src/TCDev.APIGenerator/Attributes/ApiAttribute.cs b/src/TCDev.APIGenerator/Attributes/ApiAttribute.cs
new file mode 100644
index 0000000..d36c21f
--- /dev/null
+++ b/src/TCDev.APIGenerator/Attributes/ApiAttribute.cs
@@ -0,0 +1,54 @@
+// TCDev.de 2022/04/10
+// TCDev.APIGenerator.ApiAttribute.cs
+// https://github.com/DeeJayTC/net-dynamic-api
+
+using System;
+using System.Linq;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.Filters;
+
+namespace TCDev.ApiGenerator.Attributes
+{
+ [AttributeUsage(AttributeTargets.Class)]
+ public class ApiAttribute : Attribute
+ {
+ ///
+ /// Attribute defining auto generated controller for the class
+ ///
+ /// The full base route for the class ie /myclass/
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// The methods to generate for this endpoint
+ public ApiAttribute(
+ string route,
+ ApiMethodsToGenerate methods = ApiMethodsToGenerate.All,
+ string[] requiredReadScopes = null,
+ string[] requiredWriteScopes = null,
+ bool fireEvents = false,
+ bool authorize = true,
+ bool cache = false,
+ int cacheDuration = 50000)
+ {
+ this.Route = route;
+ this.Options = new ApiAttributeAttributeOptions
+ {
+ RequiredReadScopes = requiredReadScopes,
+ RequiredWriteScopes = requiredWriteScopes,
+ Authorize = authorize,
+ Cache = cache,
+ CacheDuration = cacheDuration,
+ FireEvents = fireEvents,
+ Methods = methods
+ };
+ }
+
+ public string Route { get; set; }
+ public ApiAttributeAttributeOptions Options { get; set; }
+
+ }
+}
diff --git a/src/TCDev.APIGenerator/Attributes/GeneratedControllerAttributeOptions.cs b/src/TCDev.APIGenerator/Attributes/ApiAttributeAttributeOptions.cs
similarity index 85%
rename from src/TCDev.APIGenerator/Attributes/GeneratedControllerAttributeOptions.cs
rename to src/TCDev.APIGenerator/Attributes/ApiAttributeAttributeOptions.cs
index 12f6932..89c4938 100644
--- a/src/TCDev.APIGenerator/Attributes/GeneratedControllerAttributeOptions.cs
+++ b/src/TCDev.APIGenerator/Attributes/ApiAttributeAttributeOptions.cs
@@ -12,17 +12,17 @@ public class ApiAttributeAttributeOptions
///
/// Claims required for read access
///
- public string[] RequiredReadClaims { get; set; } = new string[0];
+ public string[] RequiredReadScopes { get; set; } = new string[0];
///
/// Claims required for write access
///
- public string[] RequiredWriteClaims { get; set; } = new string[0];
+ public string[] RequiredWriteScopes { get; set; } = new string[0];
///
/// Wether authorized access is required or not
///
- public bool Authorize { get; set; } = true;
+ public bool Authorize { get; set; } = false;
///
/// Cache responses
diff --git a/src/TCDev.APIGenerator/Attributes/ApiMethodsToGenerate.cs b/src/TCDev.APIGenerator/Attributes/ApiMethodsToGenerate.cs
new file mode 100644
index 0000000..174690f
--- /dev/null
+++ b/src/TCDev.APIGenerator/Attributes/ApiMethodsToGenerate.cs
@@ -0,0 +1,20 @@
+// TCDev 2022/03/16
+// Apache 2.0 License
+// https://www.github.com/deejaytc/dotnet-utils
+
+using System;
+
+namespace TCDev.ApiGenerator.Attributes
+{
+
+ [Flags]
+ public enum ApiMethodsToGenerate
+ {
+ Get = 1,
+ GetById = 2,
+ Insert = 4,
+ Update = 8,
+ Delete = 16,
+ All = Get | GetById | Delete | Update | Insert
+ }
+}
\ No newline at end of file
diff --git a/src/TCDev.APIGenerator/Attributes/AuthorizeAttribute.cs b/src/TCDev.APIGenerator/Attributes/AuthorizeAttribute.cs
index f288555..43a2db0 100644
--- a/src/TCDev.APIGenerator/Attributes/AuthorizeAttribute.cs
+++ b/src/TCDev.APIGenerator/Attributes/AuthorizeAttribute.cs
@@ -3,28 +3,40 @@
// https://www.github.com/deejaytc/dotnet-utils
using System;
+using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
+using TCDev.APIGenerator.Services;
namespace TCDev.ApiGenerator.Attributes
{
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
- public class PatientAuthorizeAttribute : TypeFilterAttribute
- {
- public PatientAuthorizeAttribute() : base(typeof(AuthFilter))
- {
- }
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
+ public class ApiAuthAttribute : Attribute, IAuthorizationFilter
+ {
+ private readonly AssemblyService _assemblies;
- private class AuthFilter : IActionFilter
- {
- public void OnActionExecuted(ActionExecutedContext filterContext)
- {
- throw new NotImplementedException();
- }
+ public void OnAuthorization(AuthorizationFilterContext context)
+ {
+ var _assemblies = context.HttpContext.RequestServices.GetService(typeof(AssemblyService)) as AssemblyService;
+ var _type = _assemblies.Types.FirstOrDefault(p => p.Name == "Car");
- public void OnActionExecuting(ActionExecutingContext context)
- {
- }
- }
- }
+ if (_type == null) return;
+
+ var attrs = Attribute.GetCustomAttributes(_type, typeof(ApiAttribute));
+ if (attrs.FirstOrDefault(p => p.GetType() == typeof(ApiAttribute)) is ApiAttribute optionAttrib)
+ {
+ if (!optionAttrib.Options.Authorize) return;
+
+ if (context.HttpContext.User.Identity != null && !context.HttpContext.User.Identity.IsAuthenticated)
+ {
+ context.Result = new UnauthorizedResult();
+ }
+ }
+ else
+ {
+ throw new Exception($"Could not find ApiAttribute on Class: {_type.GetType()}");
+ }
+
+ }
+ }
}
\ No newline at end of file
diff --git a/src/TCDev.APIGenerator/Attributes/GeneratedControllerAttribute.cs b/src/TCDev.APIGenerator/Attributes/GeneratedControllerAttribute.cs
deleted file mode 100644
index 9a3c829..0000000
--- a/src/TCDev.APIGenerator/Attributes/GeneratedControllerAttribute.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-// TCDev 2022/03/16
-// Apache 2.0 License
-// https://www.github.com/deejaytc/dotnet-utils
-
-using System;
-
-namespace TCDev.ApiGenerator.Attributes
-{
-
- [Flags]
- public enum ApiMethodsToGenerate
- {
- Get = 1,
- GetById = 2,
- Insert = 4,
- Update = 8,
- Delete = 16,
- All = Get | GetById | Delete | Update | Insert
- }
-
-
- [AttributeUsage(AttributeTargets.Class)]
- public class ApiAttribute : Attribute
- {
- ///
- /// Attribute defining auto generated controller for the class
- ///
- /// The full base route for the class ie /myclass/
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- /// The methods to generate for this endpoint
- public ApiAttribute(
- string route,
- ApiMethodsToGenerate methods = ApiMethodsToGenerate.All,
- string[] requiredReadClaims = null,
- string[] requiredWriteClaims = null,
- string[] requiredRolesRead = null,
- string[] requiredRolesWrite = null,
- bool fireEvents = false,
- bool authorize = true,
- bool cache = false,
- int cacheDuration = 50000)
- {
- Route = route;
- Options = new ApiAttributeAttributeOptions
- {
- RequiredReadClaims = requiredReadClaims,
- RequiredWriteClaims = requiredWriteClaims,
- Authorize = authorize,
- Cache = cache,
- CacheDuration = cacheDuration,
- FireEvents = fireEvents,
- Methods = methods
- };
- }
-
- public string Route { get; set; }
- public ApiAttributeAttributeOptions Options { get; set; }
- }
-}
\ No newline at end of file
diff --git a/src/TCDev.APIGenerator/Controller/GenericController.cs b/src/TCDev.APIGenerator/Controller/GenericController.cs
index 14d2110..0e0afd8 100644
--- a/src/TCDev.APIGenerator/Controller/GenericController.cs
+++ b/src/TCDev.APIGenerator/Controller/GenericController.cs
@@ -1,181 +1,238 @@
-// TCDev 2022/03/16
-// Apache 2.0 License
-// https://www.github.com/deejaytc/dotnet-utils
+// TCDev.de 2022/03/16
+// TCDev.APIGenerator.GenericController.cs
+// https://github.com/DeeJayTC/net-dynamic-api
using System;
using System.Collections.Generic;
+using System.Data.Entity.Core.Metadata.Edm;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Routing.Controllers;
+using Microsoft.OData.Edm;
+using Microsoft.OData.UriParser;
using TCDev.ApiGenerator.Attributes;
using TCDev.ApiGenerator.Data;
using TCDev.ApiGenerator.Interfaces;
+using TCDev.APIGenerator.Services;
namespace TCDev.ApiGenerator
{
- [Route("api/[controller]")]
- [Produces("application/json")]
- public class GenericController : ODataController
- where T : class,
- IObjectBase
- {
- public GenericController(IAuthorizationService authorizationService, IGenericRespository repository)
- {
- _repository = repository;
- _authorizationService = authorizationService;
-
- ConfigureController();
- }
-
- private readonly IAuthorizationService _authorizationService;
- private readonly IGenericRespository _repository;
- private bool useCache { get; set; }
- private bool fireEvent { get; set; }
-
- public ApiMethodsToGenerate methodsToGenerate;
-
- private void ConfigureController()
- {
- // Get attribute config from underlying type T
- var attrs = Attribute.GetCustomAttributes(typeof(T));
- if (attrs.FirstOrDefault(p => p.GetType() == typeof(ApiAttribute)) is ApiAttribute optionAttrib)
- {
- useCache = optionAttrib.Options.Cache;
- fireEvent = optionAttrib.Options.FireEvents;
- methodsToGenerate = optionAttrib.Options.Methods;
-
- // Check if we need to remove methods..
-
-
-
- }
- else
- {
- throw new Exception($"Could not find ApiAttribute on Class: {typeof(T)}");
- }
- }
-
- ///
- /// Returns a list of entries
- ///
- ///
- [Produces("application/json")]
- [ProducesErrorResponseType(typeof(BadRequestResult))]
- [HttpGet]
- [EnableQuery(
- AllowedQueryOptions = AllowedQueryOptions.All,
- AllowedFunctions = AllowedFunctions.All,
- MaxTop = 200,
- MaxSkip = 199,
- PageSize = 20)]
- public IActionResult Query()
- {
- // Check if post is enabled
- if (!methodsToGenerate.HasFlag(ApiMethodsToGenerate.Get))
- return BadRequest($"GET is disabled for {typeof(T).Name}");
-
- if (!ModelState.IsValid)
- return BadRequest();
-
- return Ok(_repository.Get());
- }
-
- [HttpGet("{id}")]
- public async Task Find(TEntityId id)
- {
- // Check if post is enabled
- if (!methodsToGenerate.HasFlag(ApiMethodsToGenerate.Get))
- return BadRequest($"GET is disabled for {typeof(T).Name}");
-
- if (!ModelState.IsValid)
- return BadRequest();
-
- var record = await _repository.GetAsync(id);
-
-
- return Ok(record);
- }
-
-
- [HttpPost]
- public async Task Create([FromBody] T record)
- {
- try
- {
+ [Route("api/[controller]")]
+ [Produces("application/json")]
+ [ApiAuthAttribute]
+ public class GenericController : ODataController
+ where T : class,
+ IObjectBase
+ {
+ private ApiAttributeAttributeOptions options;
+ private readonly IAuthorizationService authorizationService;
+ private readonly IGenericRespository repository;
+ private readonly ODataScopeLookup scopeLookup;
+
+ private void ConfigureController()
+ {
+ // Get attribute config from underlying type T
+ var attrs = Attribute.GetCustomAttributes(typeof(T));
+ if (attrs.FirstOrDefault(p => p.GetType() == typeof(ApiAttribute)) is ApiAttribute optionAttrib)
+ {
+ this.options = optionAttrib.Options;
+ }
+ else
+ {
+ throw new Exception($"Could not find ApiAttribute on Class: {typeof(T)}");
+ }
+ }
+
+ ///
+ /// Returns a list of entries
+ ///
+ ///
+ [Produces("application/json")]
+ [ProducesErrorResponseType(typeof(BadRequestResult))]
+ [HttpGet]
+ [EnableQuery(
+ AllowedQueryOptions = AllowedQueryOptions.All,
+ AllowedFunctions = AllowedFunctions.All,
+ PageSize = 20)
+ ]
+ public IActionResult Query(ODataQueryOptions ODataOpts)
+ {
+ // Check if post is enabled
+ if (!this.options.Methods.HasFlag(ApiMethodsToGenerate.Get))
+ {
+ return BadRequest($"GET is disabled for {typeof(T).Name}");
+ }
+
+ if (this.options.Authorize && !this.HttpContext.ValidateScopes(this.scopeLookup.GetRequestedScopes(ODataOpts), ""))
+ {
+ return Forbid();
+ }
+
+ if (!this.ModelState.IsValid)
+ {
+ return BadRequest();
+ }
+
+ return Ok(this.repository.Get());
+
+ }
+ [HttpGet("{id}")]
+ public async Task Find(ODataQueryOptions ODataOpts,TEntityId id )
+ {
// Check if post is enabled
- if (!methodsToGenerate.HasFlag(ApiMethodsToGenerate.Insert))
- return BadRequest($"POST is disabled for {record.GetType().Name}");
-
- // Check if payload is valid
- if (!ModelState.IsValid)
- return BadRequest();
-
- // Create the new entry
- _repository.Create(record);
- await _repository.SaveAsync();
-
- // respond with the newly created record
- return CreatedAtAction("Find", new { id = record.Id }, record);
- }
- catch (Exception ex)
- {
- return BadRequest(ex);
- }
- }
-
- [HttpPut("{id}")]
- public async Task Update(TEntityId id, [FromBody] T record)
- {
- try
- {
- if (!methodsToGenerate.HasFlag(ApiMethodsToGenerate.Update))
- return BadRequest($"PUT is disabled for {record.GetType().Name}");
-
- if (!ModelState.IsValid)
- return BadRequest();
-
- var existingRecord = await _repository.GetAsync(id);
- if (existingRecord == null) return NotFound();
-
- _repository.Update(record, existingRecord);
- await _repository.SaveAsync();
+ if (!this.options.Methods.HasFlag(ApiMethodsToGenerate.Get))
+ {
+ return BadRequest($"GET is disabled for {typeof(T).Name}");
+ }
+
+ if (this.options.Authorize && !this.HttpContext.ValidateScopes(this.scopeLookup.GetRequestedScopes(ODataOpts), ""))
+ {
+ return Forbid();
+ }
+
+ if (!this.ModelState.IsValid)
+ {
+ return BadRequest();
+ }
+
+ var record = await this.repository.GetAsync(id);
+
return Ok(record);
- }
- catch (Exception ex)
- {
- return BadRequest(ex.Message);
- }
- }
-
- [HttpDelete("{id}")]
- public async Task Delete(TEntityId id)
- {
- try
- {
- if (!methodsToGenerate.HasFlag(ApiMethodsToGenerate.Delete))
- return BadRequest($"DELETE is disabled");
-
- if (!ModelState.IsValid)
- return BadRequest();
-
- var existingRecord = await _repository.GetAsync(id);
- if (existingRecord == null) return NotFound();
-
- _repository.Delete(id);
- if (await _repository.SaveAsync() == 0)
- return BadRequest();
-
- return NoContent();
- }
- catch (Exception ex)
- {
- return BadRequest(ex.Message);
- }
- }
- }
-}
\ No newline at end of file
+
+ }
+
+
+ [HttpPost]
+ public async Task Create([FromBody] T record)
+ {
+ try
+ {
+ // Check if post is enabled
+ if (!this.options.Methods.HasFlag(ApiMethodsToGenerate.Insert))
+ {
+ return BadRequest($"POST is disabled for {record.GetType().Name}");
+ }
+
+ if (this.options.Authorize && !this.HttpContext.ValidateScopes(this.options.RequiredWriteScopes, ""))
+ {
+ return Forbid();
+ }
+
+ // Check if payload is valid
+ if (!this.ModelState.IsValid)
+ {
+ return BadRequest();
+ }
+
+ // Create the new entry
+ this.repository.Create(record);
+ await this.repository.SaveAsync();
+
+ // respond with the newly created record
+ return CreatedAtAction("Find", new
+ {
+ id = record.Id
+ }, record);
+
+ }
+ catch (Exception ex)
+ {
+ return BadRequest(ex.Message);
+ }
+ }
+
+ [HttpPut("{id}")]
+ public async Task Update(TEntityId id, [FromBody] T record)
+ {
+ try
+ {
+ if (!this.options.Methods.HasFlag(ApiMethodsToGenerate.Update))
+ {
+ return BadRequest($"PUT is disabled for {record.GetType().Name}");
+ }
+
+ if (this.options.Authorize && !this.HttpContext.ValidateScopes(this.options.RequiredWriteScopes, ""))
+ {
+ return Forbid();
+ }
+
+ if (!this.ModelState.IsValid)
+ {
+ return BadRequest();
+ }
+
+ var existingRecord = await this.repository.GetAsync(id);
+ if (existingRecord == null)
+ {
+ return NotFound();
+ }
+
+ this.repository.Update(record, existingRecord);
+ await this.repository.SaveAsync();
+
+ return Ok(record);
+ }
+ catch (Exception ex)
+ {
+ return BadRequest(ex.Message);
+ }
+ }
+
+ [HttpDelete("{id}")]
+ public async Task Delete(TEntityId id)
+ {
+ try
+ {
+ if (!this.options.Methods.HasFlag(ApiMethodsToGenerate.Delete))
+ {
+ return BadRequest("DELETE is disabled");
+ }
+
+ if (this.options.Authorize && !this.HttpContext.ValidateScopes(this.options.RequiredWriteScopes, ""))
+ {
+ return Forbid();
+ }
+
+ if (!this.ModelState.IsValid)
+ {
+ return BadRequest();
+ }
+
+ var existingRecord = await this.repository.GetAsync(id);
+ if (existingRecord == null)
+ {
+ return NotFound();
+ }
+
+ this.repository.Delete(id);
+ if (await this.repository.SaveAsync() == 0)
+ {
+ return BadRequest();
+ }
+
+ return NoContent();
+ }
+ catch (Exception ex)
+ {
+ return BadRequest(ex.Message);
+ }
+ }
+
+ public GenericController(
+ IAuthorizationService authorizationService,
+ IGenericRespository repository,
+ ODataScopeLookup scopeLookup)
+ {
+ this.repository = repository;
+ this.authorizationService = authorizationService;
+ this.scopeLookup = scopeLookup;
+
+ ConfigureController();
+ }
+ }
+}
diff --git a/src/TCDev.APIGenerator/Controller/ScopeValidator.cs b/src/TCDev.APIGenerator/Controller/ScopeValidator.cs
new file mode 100644
index 0000000..cbca258
--- /dev/null
+++ b/src/TCDev.APIGenerator/Controller/ScopeValidator.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNetCore.Http;
+
+namespace TCDev.ApiGenerator
+{
+ public static class ScopeValidator
+ {
+ public static bool ValidateScopes(this HttpContext context, IEnumerable compareScopes, string resource)
+ {
+ var userScopes = context.User.Claims
+ .FirstOrDefault(p => p.Type == "scope" || p.Type == "scp").Value
+ .Split(" ")
+ .ToArray();
+
+ return userScopes.All(compareScopes.Contains);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/TCDev.APIGenerator/Data/GenericDbContext.cs b/src/TCDev.APIGenerator/Data/GenericDbContext.cs
index 711ed46..65caa68 100644
--- a/src/TCDev.APIGenerator/Data/GenericDbContext.cs
+++ b/src/TCDev.APIGenerator/Data/GenericDbContext.cs
@@ -1,167 +1,126 @@
-// TCDev 2022/03/16
-// Apache 2.0 License
-// https://www.github.com/deejaytc/dotnet-utils
+// TCDev.de 2022/03/16
+// TCDev.APIGenerator.GenericDbContext.cs
+// https://github.com/DeeJayTC/net-dynamic-api
using System;
using System.IO;
using System.Linq;
-using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using EntityFrameworkCore.Triggers;
-using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.Extensions.Configuration;
using Microsoft.OData.Edm;
using Microsoft.OData.ModelBuilder;
-using TCDev.ApiGenerator.Attributes;
-using TCDev.ApiGenerator.Extension;
+using TCDev.APIGenerator.Services;
namespace TCDev.ApiGenerator.Data
{
- public class GenericDbContext : DbContext
- {
- public GenericDbContext()
- {
- }
-
- public GenericDbContext(
- DbContextOptions options,
- IConfiguration config,
- IHttpContextAccessor httpContextAccessor) : base(options)
- {
- HttpContextAccessor = httpContextAccessor;
- }
-
- protected IHttpContextAccessor HttpContextAccessor { get; }
-
- public static IModel StaticModel { get; } = BuildStaticModel();
- public static IEdmModel EdmModel { get; } = GetEdmModel();
-
- protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
- {
- if (!optionsBuilder.IsConfigured)
- {
- var config = new ApiGeneratorConfig(null);
- var configuration = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json")
- .Build();
- var connectionString = configuration.GetConnectionString("ApiGeneratorDatabase");
+ public class GenericDbContext : DbContext
+ {
+ //public static IModel StaticModel { get; } = BuildStaticModel();
+ private readonly AssemblyService assemblyService;
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ if (optionsBuilder.IsConfigured)
+ {
+ return;
+ }
+ var configuration = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json")
+ .AddJsonFile("secrets.json", true)
+ .Build();
+ var config = new ApiGeneratorConfig(configuration);
// Add Database Context
switch (config.DatabaseOptions.DatabaseType)
{
- case DBType.InMemory:
- optionsBuilder.UseInMemoryDatabase("ApiGeneratorDB");
- break;
- case DBType.SQL:
- optionsBuilder.UseSqlServer(connectionString);
- break;
- case DBType.SQLite:
- optionsBuilder.UseSqlite(connectionString);
- break;
- default:
- throw new Exception("Database Type Unkown");
+ case DbType.InMemory:
+ optionsBuilder.UseInMemoryDatabase("ApiGeneratorDB");
+ break;
+ case DbType.Sql:
+ var connectionStringSql = configuration.GetConnectionString("ApiGeneratorDatabase");
+ optionsBuilder.UseSqlServer(connectionStringSql);
+ break;
+ case DbType.SqLite:
+ var connectionStringSqLite = configuration.GetConnectionString("ApiGeneratorDatabase");
+ optionsBuilder.UseSqlite(connectionStringSqLite);
+ break;
+ default:
+ throw new Exception("Database Type Unknown");
+ }
+ }
+
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ // Add all types T using IEntityTypeConfiguration
+ foreach (var asm in this.assemblyService.Assemblies)
+ {
+ builder.ApplyConfigurationsFromAssembly(asm);
+ }
+
+ // Add all other custom types, not implementing IEntityTypeConfiguration
+ foreach (var customType in this.assemblyService.Types.Where(x => !x.IsAssignableFrom(typeof(IEntityTypeConfiguration<>))))
+ {
+ builder.Entity(customType);
+
+ //builder.Model.AddEntityType(customType);
}
+ base.OnModelCreating(builder);
+ }
+
+
+ ///
+ /// Generate EDM Model for OData functionality
+ ///
+ ///
+ public static IEdmModel GetEdmModel(AssemblyService service)
+ {
+ var builder = new ODataConventionModelBuilder();
+ foreach (var customType in service.Types)
+ {
+ builder.AddEntityType(customType);
+ }
+
+ return builder.GetEdmModel();
+ }
+
+
+ public GenericDbContext(
+ DbContextOptions options,
+ IConfiguration config,
+ AssemblyService assemblyService) : base(options)
+ {
+ this.assemblyService = assemblyService;
+ }
+
+ #region If you're targeting EF Core
+
+ public override int SaveChanges()
+ {
+ return this.SaveChangesWithTriggers(base.SaveChanges);
+ }
+
+ public override int SaveChanges(bool acceptAllChangesOnSuccess)
+ {
+ return this.SaveChangesWithTriggers(base.SaveChanges, acceptAllChangesOnSuccess);
+ }
+
+ public override Task SaveChangesAsync(CancellationToken cancellationToken = default)
+ {
+ return this.SaveChangesWithTriggersAsync(base.SaveChangesAsync, true, cancellationToken);
+ }
+
+ public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess,
+ CancellationToken cancellationToken = default)
+ {
+ return this.SaveChangesWithTriggersAsync(base.SaveChangesAsync, acceptAllChangesOnSuccess, cancellationToken);
+ }
- }
- }
-
- // -> Tenant Isolation
- //public void SetGlobalQuery(ModelBuilder builder) where T : EntityBase
- //{
- // var user = HttpContextAccessor.HttpContext.GetUser();
- // builder.Entity().HasKey(e => e.Id);
- // builder.Entity().HasQueryFilter(e => e.TenantId == user.TenantId);
- //}
-
- protected override void OnModelCreating(ModelBuilder builder)
- {
- // Add all types T using IEntityTypeConfiguration
- builder.ApplyConfigurationsFromAssembly(Assembly.GetEntryAssembly());
-
- // Add all other types (auto mode)
- var customTypes = Assembly.GetEntryAssembly().GetExportedTypes()
- .Where(x => x.GetCustomAttributes().Any());
- foreach (var customType in customTypes.Where(x => x.GetInterface("IEntityTypeConfiguration`1") == null))
- builder.Entity(customType);
-
- base.OnModelCreating(builder);
- }
-
-
- ///
- /// Generate EDM Model for OData functionalities
- ///
- ///
- public static IEdmModel GetEdmModel()
- {
- var customTypes = Assembly.GetEntryAssembly().GetExportedTypes()
- .Where(x => x.GetCustomAttributes().Any());
- var builder = new ODataConventionModelBuilder();
- foreach (var customType in customTypes)
- {
- var newType = builder.AddEntityType(customType);
- }
-
- return builder.GetEdmModel();
- }
-
-
- //public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
- //{
- // //var entries = ChangeTracker
- // // .Entries()
- // // .Where(e =>
- // // e.Entity is IObjectBase
- // // && (e.State == EntityState.Added
- // // || e.State == EntityState.Modified
- // // || e.State == EntityState.Deleted
- // // )
- // // );
-
- // //UpdateEntries(user, entries);
- // //UpdateEntries(user, entries);
- // //UpdateEntries(user, entries);
-
- // return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
- //}
-
-
- private static IModel BuildStaticModel()
- {
- using var dbContext = new GenericDbContext();
- return dbContext.Model;
- }
-
-
- #region If you're targeting EF Core
-
- public override int SaveChanges()
- {
- return this.SaveChangesWithTriggers(base.SaveChanges);
- }
-
- public override int SaveChanges(bool acceptAllChangesOnSuccess)
- {
- return this.SaveChangesWithTriggers(base.SaveChanges, acceptAllChangesOnSuccess);
- }
-
- public override Task SaveChangesAsync(CancellationToken cancellationToken = default)
- {
- return this.SaveChangesWithTriggersAsync(base.SaveChangesAsync, true, cancellationToken);
- }
-
- public override Task SaveChangesAsync(bool acceptAllChangesOnSuccess,
- CancellationToken cancellationToken = default)
- {
- return this.SaveChangesWithTriggersAsync(base.SaveChangesAsync, acceptAllChangesOnSuccess, cancellationToken);
- }
-
- #endregion
- }
-}
\ No newline at end of file
+ #endregion
+ }
+}
diff --git a/src/TCDev.APIGenerator/Data/GenericRepository.cs b/src/TCDev.APIGenerator/Data/GenericRepository.cs
index 8323caa..6696b99 100644
--- a/src/TCDev.APIGenerator/Data/GenericRepository.cs
+++ b/src/TCDev.APIGenerator/Data/GenericRepository.cs
@@ -1,5 +1,5 @@
-// TCDev 2022/03/16
-// Apache 2.0 License
+// TCDev.de 2022/03/16
+// TCDev.APIGenerator.GenericRepository.cs
// https://www.github.com/deejaytc/dotnet-utils
using System;
@@ -9,133 +9,148 @@
using TCDev.ApiGenerator.Interfaces;
using TCDev.APIGenerator.Schema.Interfaces;
-namespace TCDev.ApiGenerator.Data
+namespace TCDev.ApiGenerator.Data;
+
+public class GenericRespository : IGenericRespository
+ where TEntity : class, IObjectBase
{
- public class GenericRespository : IGenericRespository
- where TEntity : class, IObjectBase
+ private DbContext context;
+
+ public GenericRespository(GenericDbContext context)
{
- public GenericRespository(GenericDbContext context)
- {
- _context = context;
- }
+ this.context = context;
+ }
- private DbContext _context;
+ public IQueryable Get()
+ {
+ return this.context.Set();
+ }
- public IQueryable Get()
- {
- return _context.Set();
- }
+ public TEntity Get(TEntityId id)
+ {
+ return Get()
+ .SingleOrDefault(e => e.Id.ToString() == id.ToString());
+ }
- public TEntity Get(TEntityId id)
- {
- return Get().SingleOrDefault(e => e.Id.ToString() == id.ToString());
- }
+ public async Task GetAsync(TEntityId id)
+ {
+ return await Get()
+ .SingleOrDefaultAsync(e => e.Id.ToString() == id.ToString());
+ }
- public async Task GetAsync(TEntityId id)
- {
- return await Get().SingleOrDefaultAsync(e => e.Id.ToString() == id.ToString());
- }
+ public void Create(TEntity record)
+ {
+ var now = DateTime.UtcNow;
- public void Create(TEntity record)
- {
- var now = DateTime.UtcNow;
+ this.context.Add(record);
- _context.Add(record);
+ if (typeof(TEntity).IsAssignableFrom(typeof(IHasTrackingFields)))
+ this.context.Entry(record)
+ .Property("Created")
+ .CurrentValue = now;
+ }
- if (typeof(TEntity).IsAssignableFrom(typeof(IHasTrackingFields))) _context.Entry(record).Property("Created").CurrentValue = now;
+ public async void Update(TEntity newRecord, TEntity oldRecord)
+ {
+ // We have a before update handler
+ if (typeof(TEntity).IsAssignableTo(typeof(IBeforeUpdate)))
+ {
+ var baseEntity = newRecord as IBeforeUpdate;
+ newRecord = await baseEntity.BeforeUpdate(newRecord, oldRecord);
}
- public async void Update(TEntity newRecord, TEntity oldRecord)
+ this.context.Set()
+ .Attach(oldRecord);
+ oldRecord = newRecord;
+
+ if (typeof(TEntity).IsAssignableFrom(typeof(IHasTrackingFields)))
{
- // We have a before update handler
- if (typeof(TEntity).IsAssignableTo(typeof(IBeforeUpdate)))
- {
- var baseEntity = newRecord as IBeforeUpdate;
- newRecord = await baseEntity.BeforeUpdate(newRecord, oldRecord);
- }
+ this.context.Entry(newRecord)
+ .Property("LastModified")
+ .CurrentValue = DateTime.UtcNow;
+ this.context.Entry(newRecord)
+ .State = EntityState.Modified;
+ }
- _context.Set().Attach(oldRecord);
- oldRecord = newRecord;
+ await this.context.SaveChangesAsync();
- if (typeof(TEntity).IsAssignableFrom(typeof(IHasTrackingFields)))
- {
- _context.Entry(newRecord).Property("LastModified").CurrentValue = DateTime.UtcNow;
- _context.Entry(newRecord).State = EntityState.Modified;
- }
+ // We have a after update handler
+ if (typeof(TEntity).IsAssignableTo(typeof(IAfterUpdate)))
+ {
+ var baseEntity = newRecord as IAfterUpdate;
+ await baseEntity.AfterUpdate(newRecord, oldRecord);
+ }
+ }
- await _context.SaveChangesAsync();
+ public void Delete(TEntityId id)
+ {
+ var record = Get(id);
- // We have a after update handler
- if (typeof(TEntity).IsAssignableTo(typeof(IAfterUpdate)))
+ if (record != null)
+ {
+ // If the entity is using softdelete -> only mark as deleted
+ if (typeof(TEntity).IsAssignableFrom(typeof(ISoftDelete)))
{
- var baseEntity = newRecord as IAfterUpdate;
- await baseEntity.AfterUpdate(newRecord, oldRecord);
+ this.context.Entry(record)
+ .Property("Deleted")
+ .CurrentValue = DateTime.UtcNow;
+ this.context.Entry(record)
+ .Property("IsDeleted")
+ .CurrentValue = true;
+ this.context.Entry(record)
+ .State = EntityState.Modified;
}
- }
-
- public void Delete(TEntityId id)
- {
- var record = Get(id);
-
- if (record != null)
+ else
{
- // If the entity is using softdelete -> only mark as deleted
- if (typeof(TEntity).IsAssignableFrom(typeof(ISoftDelete)))
- {
- _context.Entry(record).Property("Deleted").CurrentValue = DateTime.UtcNow;
- _context.Entry(record).Property("IsDeleted").CurrentValue = true;
- _context.Entry(record).State = EntityState.Modified;
- }
- else
- {
- _context.Remove(record);
- }
+ this.context.Remove(record);
}
}
+ }
- public Task SaveAsync()
- {
- return _context.SaveChangesAsync();
- }
-
+ public Task SaveAsync()
+ {
+ return this.context.SaveChangesAsync();
+ }
- public int Save()
- {
- return _context.SaveChanges();
- }
- public IQueryable GetQuery(Type EntityType)
- {
- var pq = from p in GetType().GetProperties()
- where p.PropertyType.IsGenericType
- && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)
- && p.PropertyType.GenericTypeArguments[0] == EntityType
- select p;
- var prop = pq.Single();
-
- return (IQueryable) prop.GetValue(this);
- }
+ public int Save()
+ {
+ return this.context.SaveChanges();
+ }
- #region Dispose
+ public IQueryable GetQuery(Type entityType)
+ {
+ var pq =
+ from p in GetType()
+ .GetProperties()
+ where p.PropertyType.IsGenericType
+ && p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>)
+ && p.PropertyType.GenericTypeArguments[0] == entityType
+ select p;
+ var prop = pq.Single();
+
+ return (IQueryable)prop.GetValue(this);
+ }
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
+ #region Dispose
- protected virtual void Dispose(bool disposing)
- {
- if (disposing)
- if (_context != null)
- {
- _context.Dispose();
- _context = null;
- }
- }
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
- #endregion
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ if (this.context != null)
+ {
+ this.context.Dispose();
+ this.context = null;
+ }
}
-}
\ No newline at end of file
+
+ #endregion
+}
diff --git a/src/TCDev.APIGenerator/Extension/ApiGeneratorConfig.cs b/src/TCDev.APIGenerator/Extension/ApiGeneratorConfig.cs
index a8d9854..b561a3f 100644
--- a/src/TCDev.APIGenerator/Extension/ApiGeneratorConfig.cs
+++ b/src/TCDev.APIGenerator/Extension/ApiGeneratorConfig.cs
@@ -1,82 +1,105 @@
-// TCDev 2022/03/16
-// Apache 2.0 License
-// https://www.github.com/deejaytc/dotnet-utils
+// TCDev.de 2022/03/16
+// TCDev.APIGenerator.ApiGeneratorConfig.cs
+// https://github.com/DeeJayTC/net-dynamic-api
-using System;
using System.IO;
using Microsoft.Extensions.Configuration;
-namespace TCDev.ApiGenerator.Extension
+namespace TCDev.ApiGenerator;
+
+public class ApiGeneratorConfig
{
- public class ApiGeneratorConfig
+ public CacheOptions CacheOptions { get; set; } = new();
+ public ApiOptions ApiOptions { get; set; } = new();
+ public SwaggerOptions SwaggerOptions { get; set; } = new();
+ public DatabaseOptions DatabaseOptions { get; set; } = new();
+ public ODataFunctions ODataOptions { get; set; } = new();
+ public IdentityOptions IdentityOptions { get; set; } = new();
+ private readonly IConfiguration configuration;
+
+ public ApiGeneratorConfig(IConfiguration config)
{
- IConfiguration configuration;
- public ApiGeneratorConfig(IConfiguration config)
- {
- configuration = config;
- if(configuration == null) {
- configuration = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json")
- .AddJsonFile("secrets.json", true)
- .AddEnvironmentVariables()
- .Build();
- }
+ this.configuration = config
+ ?? new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json")
+ .AddJsonFile("apiGeneratorConfig.json",true)
+ .AddJsonFile("secrets.json", true)
+ .AddEnvironmentVariables()
+ .Build();
- //Load Options
- configuration.Bind("Api:Cache", CacheOptions);
- configuration.Bind("Api:Swagger", SwaggerOptions);
- configuration.Bind("Api:Database", DatabaseOptions);
- }
+ //Load Options
+ this.configuration.Bind("Api:Basic", this.ApiOptions);
+ this.configuration.Bind("Api:Cache", this.CacheOptions);
+ this.configuration.Bind("Api:Swagger", this.SwaggerOptions);
+ this.configuration.Bind("Api:Database", this.DatabaseOptions);
+ this.configuration.Bind("Api:Odata", this.ODataOptions);
+ this.configuration.Bind("Api:Identity", this.IdentityOptions);
+ }
+}
- private readonly IConfigurationRoot Configuration;
- public CacheOptions CacheOptions { get; set; } = new CacheOptions();
- public SwaggerOptions SwaggerOptions { get; set; } = new SwaggerOptions();
- public DatabaseOptions DatabaseOptions { get; set; } = new DatabaseOptions();
+public class ApiOptions
+{
+ public bool UseXmlComments { get; set; } = false;
+ public string XmlCommentsFile { get; set; } = string.Empty;
+}
- public string MetadataRoute { get; set; } = "odata";
- }
+public class CacheOptions
+{
+ public bool Enabled { get; set; } = true;
+ public string Connection { get; set; } = "redis";
+}
- public class CacheOptions
- {
- public bool Enabled { get; set; } = true;
- public string Connection { get; set; } = "redis";
- }
+public enum DbType
+{
+ InMemory,
+ Sql,
+ //Postgres,
+ SqLite
+}
- public enum DBType
- {
- InMemory,
- SQL,
- //Postgres,
- SQLite
- }
+public class DatabaseOptions
+{
+ public DbType DatabaseType { get; set; } = DbType.InMemory;
+ public string? Connection { get; set; } = string.Empty;
+ public bool EnableAutomaticMigration { get; set; } = true;
+}
- public class DatabaseOptions
- {
- public DBType DatabaseType { get; set; } = DBType.InMemory;
- public string? Connection { get; set; } = String.Empty;
- public bool EnableAutomaticMigration { get; set; } = true;
- }
+public class ODataFunctions
+{
+ public bool Enabled { get; set; } = false;
+ public bool EnableSelect { get; set; } = true;
+ public bool EnableFilter { get; set; } = true;
+ public bool EnableSort { get; set; } = true;
+}
- public class ODataFunctions
- {
- public bool EnableSelect { get; set; } = true;
- public bool EnableFilter { get; set; } = true;
- public bool EnableSort { get; set; } = true;
- }
+public class SwaggerOptions
+{
+ ///
+ /// Enable Swagger in Production
+ ///
+ public bool EnableProduction { get; set; } = true;
- public class SwaggerOptions
- {
- ///
- /// Enable Swagger in Production
- ///
- public bool EnableProduction { get; set; } = true;
- public string Description { get; set; } = "Sample for TCDev API Generator";
- public string Version { get; set; } = "v1";
- public string Title { get; set; } = "TCDev Api Generator Demo";
- public string ContactMail { get; set; } = "test@test.de";
- public string ContactUri { get; set; } = "https://www.test.de";
- public string Route { get; set; } = "/swagger/v1/swagger.json";
- }
+ public string Description { get; set; } = "Sample for TCDev API Generator";
+ public string Version { get; set; } = "v1";
+ public string Title { get; set; } = "TCDev Api Generator Demo";
+ public string ContactMail { get; set; } = "test@test.de";
+ public string ContactUri { get; set; } = "https://www.test.de";
+ public string Route { get; set; } = "/swagger/v1/swagger.json";
}
+
+public class IdentityOptions
+{
+ public string EnableIdentity { get; set; } = "false";
+ public string Audience { get; set; } = "TCDevApiGenerator";
+ public string Authority { get; set; } = "https://localhost:44300";
+ public string[] Scopes { get; set; } = { "ReadWrite.All" };
+
+ public bool ValidateIssuer { get; set; } = true;
+ public string MetaDataUri { get; set; } = "";
+
+ public bool ValidateAudience { get; set; } = true;
+ public bool ValidateLifetime { get; set; } = true;
+ public bool ValidateIssuerSigningKey { get; set; } = true;
+}
\ No newline at end of file
diff --git a/src/TCDev.APIGenerator/Extension/ApiGeneratorExtension.cs b/src/TCDev.APIGenerator/Extension/ApiGeneratorExtension.cs
index 1760972..d883f73 100644
--- a/src/TCDev.APIGenerator/Extension/ApiGeneratorExtension.cs
+++ b/src/TCDev.APIGenerator/Extension/ApiGeneratorExtension.cs
@@ -1,140 +1,207 @@
-// TCDev 2022/03/16
-// Apache 2.0 License
-// https://www.github.com/deejaytc/dotnet-utils
+// TCDev.de 2022/03/16
+// TCDev.APIGenerator.ApiGeneratorExtension.cs
+// https://github.com/DeeJayTC/net-dynamic-api
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
using System.Reflection;
+using System.Text.Json.Serialization;
+using EFCore.AutomaticMigrations;
using EntityFramework.Triggers;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.OData;
+using Microsoft.AspNetCore.Routing;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.OpenApi.Models;
+using Newtonsoft.Json;
+using Swashbuckle.AspNetCore.SwaggerUI;
+using TCDev.ApiGenerator.Attributes;
using TCDev.ApiGenerator.Data;
-using TCDev.Controllers;
-using Microsoft.AspNetCore.Routing;
using TCDev.APIGenerator.Extension;
-using Microsoft.OpenApi.Models;
-using EFCore.AutomaticMigrations;
-using System;
+using TCDev.APIGenerator.Extension.Swagger;
+using TCDev.ApiGenerator.Json;
+using TCDev.APIGenerator.Schema;
+using TCDev.APIGenerator.Services;
+using TCDev.Controllers;
namespace TCDev.ApiGenerator.Extension
{
- public static class ApiGeneratorExtension
- {
-
- public static ApiGeneratorConfig ApiGeneratorConfig { get; set; } = new ApiGeneratorConfig(null);
-
- public static IServiceCollection AddApiGeneratorServices(
- this IServiceCollection services,
- IConfiguration config,
- Assembly assembly)
- {
-
- ApiGeneratorConfig = new ApiGeneratorConfig(config);
-
- // Add Database Context
-
- switch(ApiGeneratorConfig.DatabaseOptions.DatabaseType)
- {
- case DBType.InMemory:
- services.AddDbContext(options =>
- options.UseInMemoryDatabase("ApiGeneratorDB"));
- break;
- case DBType.SQL:
- services.AddDbContext(options =>
- options.UseSqlServer(config.GetConnectionString("ApiGeneratorDatabase"),
- b => b.MigrationsAssembly(assembly.FullName)));
- break;
- case DBType.SQLite:
- services.AddDbContext(options =>
- options.UseSqlite(config.GetConnectionString("ApiGeneratorDatabase"),
- b => b.MigrationsAssembly(assembly.FullName)));
- break;
- default:
- throw new Exception("Database Type Unkown");
- }
-
- services
- .AddSingleton(typeof(ITriggers<,>), typeof(Triggers<,>))
- .AddSingleton(typeof(ITriggers<>), typeof(Triggers<>))
- .AddSingleton(typeof(ITriggers), typeof(Triggers));
-
- // Add Services
- services.AddScoped(typeof(IGenericRespository<,>), typeof(GenericRespository<,>));
-
-
- //Add Framework Services & Options, we use the current assembly to get classes.
- // Todo: Add option to add any custom assembly
- services.AddMvc(o =>
- o.Conventions.Add(new GenericControllerRouteConvention()))
- .ConfigureApplicationPartManager(m =>
- m.FeatureProviders.Add(new GenericTypeControllerFeatureProvider(new[] { assembly.FullName }))
- );
-
-
- services.AddSwaggerGen(c =>
- {
- c.SwaggerDoc(ApiGeneratorConfig.SwaggerOptions.Version,
- new OpenApiInfo
- {
- Title = ApiGeneratorConfig.SwaggerOptions.Title,
- Version = ApiGeneratorConfig.SwaggerOptions.Version,
- Description = ApiGeneratorConfig.SwaggerOptions.Description,
- Contact = new OpenApiContact() {
- Email = ApiGeneratorConfig.SwaggerOptions.ContactMail,
- Url = new System.Uri(ApiGeneratorConfig.SwaggerOptions.ContactUri)
- }
- });
-
- c.DocumentFilter();
- //c.IncludeXmlComments($"{assembly.GetName().Name}.xml", true);
- });
-
-
- services.AddControllers().AddOData(opt =>
+ public static class ApiGeneratorExtension
+ {
+ public static ApiGeneratorConfig ApiGeneratorConfig { get; set; } = new(null);
+
+ public static IServiceCollection AddApiGeneratorServices(
+ this IServiceCollection services,
+ IConfiguration config,
+ Assembly assembly)
+ {
+ ApiGeneratorConfig = new ApiGeneratorConfig(config);
+
+ // Add Database Context
+
+ switch (ApiGeneratorConfig.DatabaseOptions.DatabaseType)
{
- opt.AddRouteComponents("odata", GenericDbContext.EdmModel);
- opt.EnableNoDollarQueryOptions = true;
- opt.EnableQueryFeatures(20000);
- opt.Select().Expand().Filter();
+ case DbType.InMemory:
+ services.AddDbContext(options =>
+ options.UseInMemoryDatabase("ApiGeneratorDB"));
+ break;
+ case DbType.Sql:
+ services.AddDbContext(options =>
+ options.UseSqlServer(config.GetConnectionString("ApiGeneratorDatabase"),
+ b => b.MigrationsAssembly(assembly.FullName)));
+ break;
+
+
+ case DbType.SqLite:
+ services.AddDbContext(options =>
+ options.UseSqlite(config.GetConnectionString("ApiGeneratorDatabase"),
+ b => b.MigrationsAssembly(assembly.FullName)));
+ break;
+ default:
+ throw new Exception("Database Type Unkown");
}
- );
+ services
+ .AddSingleton(typeof(ITriggers<,>), typeof(Triggers<,>))
+ .AddSingleton(typeof(ITriggers<>), typeof(Triggers<>))
+ .AddSingleton(typeof(ITriggers), typeof(Triggers))
+ .AddScoped(typeof(ODataScopeLookup<,>))
+ .AddScoped(typeof(IGenericRespository<,>), typeof(GenericRespository<,>));
- return services;
- }
- public static IApplicationBuilder UseAutomaticAPIMigrations(this IApplicationBuilder app, bool AllowDataLoss = false)
- {
- using(var serviceScope = app.ApplicationServices.CreateScope())
- {
- var dbContext = serviceScope.ServiceProvider.GetService();
- if(ApiGeneratorConfig.DatabaseOptions.DatabaseType != DBType.InMemory) {
- dbContext.MigrateToLatestVersion(new DbMigrationsOptions
- {
- AutomaticMigrationsEnabled = true,
- AutomaticMigrationDataLossAllowed = AllowDataLoss
- });
+
+
+ //Add Framework Services & Options, we use the current assembly to get classes.
+ var assemblyService = new AssemblyService();
+ services.AddSingleton(assemblyService);
+
+ var jsonDefs = JsonConvert.DeserializeObject>(File.ReadAllText("./ApiDefinition.json"));
+ assemblyService.Types.AddRange(JsonClassBuilder.CreateTypes(jsonDefs));
+ assemblyService.Types.AddRange(assembly.GetExportedTypes()
+ .Where(x => x.GetCustomAttributes()
+ .Any()));
+
+
+ // Put everything together
+ services.AddMvc(options =>
+ options.Conventions.Add(new GenericControllerRouteConvention()))
+ .ConfigureApplicationPartManager(manager =>
+ // Add our controller feature provider
+ manager.FeatureProviders.Add(new GenericTypeControllerFeatureProvider(assemblyService.Types))
+ );
+
+
+ services.AddSwaggerGen(c =>
+ {
+ c.SwaggerDoc(ApiGeneratorConfig.SwaggerOptions.Version,
+ new OpenApiInfo
+ {
+ Title = ApiGeneratorConfig.SwaggerOptions.Title,
+ Version = ApiGeneratorConfig.SwaggerOptions.Version,
+ Description = ApiGeneratorConfig.SwaggerOptions.Description,
+ Contact = new OpenApiContact
+ {
+ Email = ApiGeneratorConfig.SwaggerOptions.ContactMail, Url = new Uri(ApiGeneratorConfig.SwaggerOptions.ContactUri)
+ }
+ });
+ c.DocumentFilter();
+ c.SchemaFilter();
+ c.OperationFilter();
+
+ if (ApiGeneratorConfig.ODataOptions.Enabled)
+ {
+ c.OperationFilter();
+ }
+
+ if (ApiGeneratorConfig.ApiOptions.UseXmlComments)
+ {
+ if (!string.IsNullOrEmpty(ApiGeneratorConfig.ApiOptions.XmlCommentsFile))
+ {
+ throw new Exception("You need to set XMLCommentsFile option when using XMl Comments");
+ }
+
+ c.IncludeXmlComments(ApiGeneratorConfig.ApiOptions.XmlCommentsFile, true);
+ }
+ });
+
+
+ if (ApiGeneratorConfig.ODataOptions.Enabled)
+ {
+ services
+ .AddControllers()
+ .AddOData(opt =>
+ {
+ opt.AddRouteComponents("odata", GenericDbContext.GetEdmModel(assemblyService));
+ opt.EnableNoDollarQueryOptions = true;
+ opt.EnableQueryFeatures(20000);
+ opt.Select()
+ .Expand()
+ .OrderBy()
+ .SetMaxTop(10000)
+ .Count()
+ .SkipToken()
+ .Filter();
+ })
+ .AddJsonOptions(o => { o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); }
+ );
+ }
+ else
+ {
+ services
+ .AddControllers()
+ .AddJsonOptions(o => { o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); });
}
- }
-
- return app;
- }
- public static IApplicationBuilder UseApiGenerator(this IApplicationBuilder app)
- {
+ return services;
+ }
- app.UseSwagger();
- app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", $"{ApiGeneratorConfig.SwaggerOptions.Title} {ApiGeneratorConfig.SwaggerOptions.Version}"));
- return app;
- }
- public static IEndpointRouteBuilder UseApiGeneratorEndpoints(this IEndpointRouteBuilder builder)
- {
- return builder;
- }
+ public static IApplicationBuilder UseAutomaticApiMigrations(this IApplicationBuilder app, bool allowDataLoss = false)
+ {
+ using var serviceScope = app.ApplicationServices.CreateScope();
+ var dbContext = serviceScope.ServiceProvider.GetService();
+ if (ApiGeneratorConfig.DatabaseOptions.DatabaseType != DbType.InMemory)
+ {
+ dbContext.MigrateToLatestVersion(new DbMigrationsOptions
+ {
+ AutomaticMigrationsEnabled = true,
+ AutomaticMigrationDataLossAllowed = allowDataLoss
+ });
+ }
+ return app;
+ }
- }
-}
\ No newline at end of file
+ public static IApplicationBuilder UseApiGenerator(this IApplicationBuilder app)
+ {
+ app.UseSwagger();
+ app.UseSwaggerUI(c =>
+ {
+ c.InjectStylesheet("/SwaggerDarkTheme.css");
+ c.OAuthConfigObject = new OAuthConfigObject()
+ {
+ AppName = "ApiGenerator",
+ ClientId = string.Empty,
+ ClientSecret = string.Empty,
+
+ };
+ c.SwaggerEndpoint(
+ "/swagger/v1/swagger.json",
+ $"{ApiGeneratorConfig.SwaggerOptions.Title} {ApiGeneratorConfig.SwaggerOptions.Version}"
+ );
+ });
+
+ return app;
+ }
+
+ public static IEndpointRouteBuilder UseApiGeneratorEndpoints(this IEndpointRouteBuilder builder)
+ {
+ return builder;
+ }
+ }
+}
diff --git a/src/TCDev.APIGenerator/Extension/Auth/ScopeHandler.cs b/src/TCDev.APIGenerator/Extension/Auth/ScopeHandler.cs
new file mode 100644
index 0000000..883be1f
--- /dev/null
+++ b/src/TCDev.APIGenerator/Extension/Auth/ScopeHandler.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+
+namespace TCDev.APIGenerator.Extension.Auth
+{
+
+ public class HasScopeRequirement : IAuthorizationRequirement
+ {
+ public string Issuer { get; }
+ public string Scope { get; }
+
+ public HasScopeRequirement(string scope, string issuer)
+ {
+ this.Scope = scope ?? throw new ArgumentNullException(nameof(scope));
+ this.Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer));
+ }
+ }
+
+
+ public class HasScopeHandler : AuthorizationHandler
+ {
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasScopeRequirement requirement)
+ {
+ // If user does not have the scope claim, get out of here
+ if (!context.User.HasClaim(c => c.Type == "scope" && c.Issuer == requirement.Issuer))
+ return Task.CompletedTask;
+
+ // Split the scopes string into an array
+ var scopes = context.User.FindFirst(c => c.Type == "scope" && c.Issuer == requirement.Issuer).Value.Split(' ');
+
+ // Succeed if the scope array contains the required scope
+ if (scopes.Any(s => s == requirement.Scope))
+ context.Succeed(requirement);
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/TCDev.APIGenerator/Extension/Auth/ServiceExtension.cs b/src/TCDev.APIGenerator/Extension/Auth/ServiceExtension.cs
new file mode 100644
index 0000000..b7d6083
--- /dev/null
+++ b/src/TCDev.APIGenerator/Extension/Auth/ServiceExtension.cs
@@ -0,0 +1,49 @@
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.IdentityModel.Tokens;
+using TCDev.ApiGenerator;
+
+namespace TCDev.APIGenerator.Identity
+{
+ public static class ServiceExtension
+ {
+ public static IServiceCollection AddApiGeneratorIdentity(this IServiceCollection services, IConfiguration configuration)
+ {
+ var config = new ApiGeneratorConfig(configuration);
+
+ services
+ .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
+ .AddJwtBearer(options =>
+ {
+ options.Authority = config.IdentityOptions.Authority;
+ options.Audience = config.IdentityOptions.Audience;
+ options.RequireHttpsMetadata = false;
+ options.MetadataAddress = config.IdentityOptions.MetaDataUri;
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuer = config.IdentityOptions.ValidateIssuer,
+ ValidateLifetime = config.IdentityOptions.ValidateLifetime,
+ ValidateIssuerSigningKey = config.IdentityOptions.ValidateIssuerSigningKey,
+ NameClaimType = ClaimTypes.NameIdentifier
+ };
+ });
+ JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
+
+ return services;
+ }
+
+ public static IApplicationBuilder UseApiGeneratorAuthentication(this IApplicationBuilder app)
+ {
+ app.UseAuthentication();
+ app.UseAuthorization();
+
+ return app;
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/TCDev.APIGenerator/Extension/Swagger/EnableQueryFilter.cs b/src/TCDev.APIGenerator/Extension/Swagger/EnableQueryFilter.cs
new file mode 100644
index 0000000..9e9eb0e
--- /dev/null
+++ b/src/TCDev.APIGenerator/Extension/Swagger/EnableQueryFilter.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace TCDev.APIGenerator.Extension.Swagger
+{
+ class EnableQueryFiler : IOperationFilter
+ {
+ static List s_Parameters = (new List<(string Name, string Description)>()
+ {
+ ( "$top", "The max number of records."),
+ ( "$skip", "The number of records to skip."),
+ ( "$filter", "A function that must evaluate to true for a record to be returned."),
+ ( "$select", "Specifies a subset of properties to return. Use a comma separated list."),
+ ( "$orderby", "Determines what values are used to order a collection of records."),
+ ( "$expand", "Use to add related query data.")
+ }).Select(pair => new OpenApiParameter
+ {
+ Name = pair.Name,
+ Required = false,
+ Schema = new OpenApiSchema { Type = "String" },
+ In = ParameterLocation.Query,
+ Description = pair.Description,
+
+ }).ToList();
+
+ public void Apply(OpenApiOperation operation, OperationFilterContext context)
+ {
+ if (context.ApiDescription.ActionDescriptor.EndpointMetadata.Any(em => em is Microsoft.AspNetCore.OData.Query.EnableQueryAttribute))
+ {
+
+ operation.Parameters ??= new List();
+ foreach (var item in s_Parameters)
+ operation.Parameters.Add(item);
+ }
+ }
+ }
+}
diff --git a/src/TCDev.APIGenerator/Extension/Swagger/IgnoreODataQueryOptionOperationFilter.cs b/src/TCDev.APIGenerator/Extension/Swagger/IgnoreODataQueryOptionOperationFilter.cs
new file mode 100644
index 0000000..f4a8d78
--- /dev/null
+++ b/src/TCDev.APIGenerator/Extension/Swagger/IgnoreODataQueryOptionOperationFilter.cs
@@ -0,0 +1,39 @@
+// TCDev.de 2022/04/15
+// TCDev.APIGenerator.IgnoreODataQueryOptionOperationFilter.cs
+// https://github.com/DeeJayTC/net-dynamic-api
+
+using System.Linq;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace TCDev.APIGenerator.Extension
+{
+ public class IgnoreODataQueryOptionOperationFilter : IOperationFilter
+ {
+
+ public void Apply(OpenApiOperation operation, OperationFilterContext context)
+ {
+ context.ApiDescription.ParameterDescriptions.ToList()
+ .ForEach(x =>
+ {
+ if (x.Name == "ODataOpts")
+ {
+ if (operation.Parameters.Any(x => x.Name == "ODataOpts"))
+ {
+ try
+ {
+ operation.Parameters.Remove(operation.Parameters.Single(p => x.Name == "ODataOpts"));
+ }
+ catch
+ {
+
+ }
+
+ }
+ }
+
+ });
+
+ }
+ }
+}
diff --git a/src/TCDev.APIGenerator/Extension/SwaggerExcludeDisabledFunctions.cs b/src/TCDev.APIGenerator/Extension/Swagger/ShowInSwaggerFilter.cs
similarity index 100%
rename from src/TCDev.APIGenerator/Extension/SwaggerExcludeDisabledFunctions.cs
rename to src/TCDev.APIGenerator/Extension/Swagger/ShowInSwaggerFilter.cs
diff --git a/src/TCDev.APIGenerator/Extension/Swagger/SwaggerSchemaFilter.cs b/src/TCDev.APIGenerator/Extension/Swagger/SwaggerSchemaFilter.cs
new file mode 100644
index 0000000..a4b8e8c
--- /dev/null
+++ b/src/TCDev.APIGenerator/Extension/Swagger/SwaggerSchemaFilter.cs
@@ -0,0 +1,39 @@
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc.ApiExplorer;
+using Microsoft.AspNetCore.OData.Query;
+using TCDev.ApiGenerator.Attributes;
+
+namespace TCDev.APIGenerator.Extension
+{
+ public class SwaggerSchemaFilter : ISchemaFilter
+ {
+ public void Apply(OpenApiSchema schema, SchemaFilterContext context)
+ {
+ if (schema?.Properties == null)
+ {
+ return;
+ }
+
+ var ignoreDataMemberProperties = context.Type.GetProperties()
+ .Where(t => t.GetCustomAttribute() != null);
+
+ foreach (var ignoreDataMemberProperty in ignoreDataMemberProperties)
+ {
+ var propertyToHide = schema.Properties.Keys
+ .SingleOrDefault(x => string.Equals(x, ignoreDataMemberProperty.Name, StringComparison.CurrentCultureIgnoreCase));
+
+ if (propertyToHide != null)
+ {
+ schema.Properties.Remove(propertyToHide);
+ }
+ }
+ }
+ }
+}
diff --git a/src/TCDev.APIGenerator/FeatureProvider/GenericControllerRouteConvention.cs b/src/TCDev.APIGenerator/FeatureProvider/GenericControllerRouteConvention.cs
index dbdf92d..212755b 100644
--- a/src/TCDev.APIGenerator/FeatureProvider/GenericControllerRouteConvention.cs
+++ b/src/TCDev.APIGenerator/FeatureProvider/GenericControllerRouteConvention.cs
@@ -2,6 +2,7 @@
// Apache 2.0 License
// https://www.github.com/deejaytc/dotnet-utils
+using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
@@ -16,31 +17,28 @@ namespace TCDev.Controllers
///
public class GenericControllerRouteConvention : IControllerModelConvention
{
-
public void Apply(ControllerModel controller)
{
- if (controller.ControllerType.IsGenericType)
- {
- var genericType = controller.ControllerType.GenericTypeArguments[0];
- var customNameAttribute = genericType.GetCustomAttribute();
- controller.ControllerName = genericType.Name;
+ if (!controller.ControllerType.IsGenericType) return;
- if (customNameAttribute?.Route != null)
- {
- if (controller.Selectors.Count > 0)
- {
- var currentSelector = controller.Selectors[0];
- currentSelector.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(customNameAttribute.Route));
- }
- else
- {
- controller.Selectors.Add(new SelectorModel
- {
- AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(customNameAttribute.Route))
- });
- }
- }
- }
+ var genericType = controller.ControllerType.GenericTypeArguments[0];
+ var customNameAttribute = genericType.GetCustomAttribute();
+ controller.ControllerName = genericType.Name;
+
+ if (customNameAttribute?.Route == null) return;
+
+ if (controller.Selectors.Count > 0)
+ {
+ var currentSelector = controller.Selectors[0];
+ currentSelector.AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(customNameAttribute.Route));
+ }
+ else
+ {
+ controller.Selectors.Add(new SelectorModel
+ {
+ AttributeRouteModel = new AttributeRouteModel(new RouteAttribute(customNameAttribute.Route))
+ });
+ }
}
}
}
\ No newline at end of file
diff --git a/src/TCDev.APIGenerator/FeatureProvider/GenericTypeControllerFeatureProvider.cs b/src/TCDev.APIGenerator/FeatureProvider/GenericTypeControllerFeatureProvider.cs
index 0a3a994..156c962 100644
--- a/src/TCDev.APIGenerator/FeatureProvider/GenericTypeControllerFeatureProvider.cs
+++ b/src/TCDev.APIGenerator/FeatureProvider/GenericTypeControllerFeatureProvider.cs
@@ -2,6 +2,7 @@
// Apache 2.0 License
// https://www.github.com/deejaytc/dotnet-utils
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -12,25 +13,24 @@
namespace TCDev.Controllers
{
- public class GenericTypeControllerFeatureProvider : IApplicationFeatureProvider
+ public class GenericAssemblyControllerFeatureProvider : IApplicationFeatureProvider
{
///
/// Initiate the Controller generator
///
/// Names of assemblies to search for classes
- public GenericTypeControllerFeatureProvider(string[] assemblies)
+ public GenericAssemblyControllerFeatureProvider(Assembly[] assemblies)
{
- Assemblies = assemblies;
+ this.Assemblies = assemblies;
}
- private string[] Assemblies { get; }
+ private Assembly[] Assemblies { get; }
public void PopulateFeature(IEnumerable parts, ControllerFeature feature)
{
foreach (var assembly in Assemblies)
{
- var loadedAssembly = Assembly.Load(assembly);
- var customClasses = loadedAssembly.GetExportedTypes().Where(x => x.GetCustomAttributes().Any());
+ var customClasses = assembly.GetExportedTypes().Where(x => x.GetCustomAttributes().Any());
foreach (var candidate in customClasses)
{
@@ -48,4 +48,36 @@ public void PopulateFeature(IEnumerable parts, ControllerFeatur
}
}
}
+
+
+ public class GenericTypeControllerFeatureProvider : IApplicationFeatureProvider
+ {
+ ///
+ /// Initiate the Controller generator
+ ///
+ /// Names of assemblies to search for classes
+ public GenericTypeControllerFeatureProvider(List types)
+ {
+ this.Types = types;
+ }
+
+ private List Types { get; }
+
+ public void PopulateFeature(IEnumerable parts, ControllerFeature feature)
+ {
+ foreach (var type in this.Types)
+ {
+ // Ignore BaseController itself
+ if (type.FullName != null && type.FullName.Contains("BaseController")) continue;
+
+ // Generate type info for our runtime controller, assign class as T
+ var propertyType = type.GetProperty("Id")?.PropertyType;
+ if (propertyType == null) continue;
+ var typeInfo = typeof(GenericController<,>).MakeGenericType(type, propertyType).GetTypeInfo();
+
+ // Finally add the new controller via FeatureProvider ->
+ feature.Controllers.Add(typeInfo);
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/TCDev.APIGenerator/Model/BaseConfig.cs b/src/TCDev.APIGenerator/Model/BaseConfig.cs
index 2850f89..f19d363 100644
--- a/src/TCDev.APIGenerator/Model/BaseConfig.cs
+++ b/src/TCDev.APIGenerator/Model/BaseConfig.cs
@@ -1,22 +1,19 @@
-// TCDev 2022/03/16
-// Apache 2.0 License
+// TCDev.de 2022/03/16
+// TCDev.APIGenerator.BaseConfig.cs
// https://www.github.com/deejaytc/dotnet-utils
using Microsoft.EntityFrameworkCore;
-namespace TCDev.ApiGenerator.Model
+namespace TCDev.ApiGenerator.Model;
+
+public enum DbTypes
{
- public enum DbTypes
- {
- Sql = 0
- , Postgres = 1
- , MySQL = 2
- }
+ Sql = 0, Postgres = 1, MySql = 2
+}
- [Keyless]
- public class BaseConfig
- {
- public string DbConnection { get; set; }
- public DbTypes DbType { get; set; } = DbTypes.Sql;
- }
-}
\ No newline at end of file
+[Keyless]
+public class BaseConfig
+{
+ public string DbConnection { get; set; }
+ public DbTypes DbType { get; set; } = DbTypes.Sql;
+}
diff --git a/src/TCDev.APIGenerator/Services/AssemblyService.cs b/src/TCDev.APIGenerator/Services/AssemblyService.cs
new file mode 100644
index 0000000..a853822
--- /dev/null
+++ b/src/TCDev.APIGenerator/Services/AssemblyService.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace TCDev.APIGenerator.Services
+{
+ public class AssemblyService
+ {
+ public List Assemblies { get; set; } = new List();
+ public List Types { get; set; } = new List();
+ }
+}
diff --git a/src/TCDev.APIGenerator/Services/Generator.cs b/src/TCDev.APIGenerator/Services/Generator.cs
new file mode 100644
index 0000000..2386b82
--- /dev/null
+++ b/src/TCDev.APIGenerator/Services/Generator.cs
@@ -0,0 +1,122 @@
+// TCDev.de 2022/04/05
+// TCDev.APIGenerator.Generator.cs
+// https://www.github.com/deejaytc/dotnet-utils
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using TCDev.ApiGenerator.Interfaces;
+using TCDev.APIGenerator.Schema;
+
+namespace TCDev.ApiGenerator.Json;
+
+
+public class JsonClassBuilder
+{
+ public static List CreateTypes(List definitions)
+ {
+ try
+ {
+ var trees = definitions.Select(def => CreateTree(def))
+ .ToList();
+
+ MetadataReference[] assemblies = AppDomain.CurrentDomain.GetAssemblies()
+ .Where(p => !p.IsDynamic)
+ .Where(a => !string.IsNullOrEmpty(a.Location))
+ .Select(a => MetadataReference.CreateFromFile(a.Location))
+ .ToArray();
+
+ var compilation = CSharpCompilation
+ .Create("TCDev.ApiGenerator")
+ .AddSyntaxTrees(trees)
+ .AddReferences(assemblies)
+ .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+ using var ms = new MemoryStream();
+ var result = compilation.Emit(ms);
+
+ if (result.Success)
+ {
+ ms.Seek(0, SeekOrigin.Begin);
+ var assembly = Assembly.Load(ms.ToArray());
+ return assembly.ExportedTypes.ToList();
+ }
+
+ var failures = result.Diagnostics.Where(diagnostic =>
+ diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error);
+
+
+ foreach (var diagnostic in failures)
+ {
+ Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage());
+ }
+
+ throw new Exception("Failed to parse JSON Definitions, could not compile assemblies",
+ new Exception(string.Join(",", failures.Select(p => p.GetMessage()))));
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ throw;
+ }
+ }
+
+ public static SyntaxTree CreateTree(JsonClassDefinition definition)
+ {
+ var classCode = $@" // Auto-generated code
+ using System;
+ using Swashbuckle.AspNetCore.Annotations;
+ using System.ComponentModel.DataAnnotations;
+ using System.ComponentModel.DataAnnotations.Schema;
+ using System.Text.Json.Serialization;
+ using TCDev.ApiGenerator.Attributes;
+ using TCDev.ApiGenerator.Interfaces;
+
+ namespace TCDev.ApiGenerator
+ {{
+ [Api(""{definition.RouteTemplate}"" ";
+
+ if (definition.Authorize)
+ {
+ classCode += $@",authorize: true";
+ if(definition.ScopesRead != string.Empty) classCode += $@",requiredReadScopes: new string[] {{ { definition.ScopesRead } }}";
+ if (definition.ScopesWrite != string.Empty) classCode += $@",requiredWriteScopes: new string[] {{ { definition.ScopesWrite } }}";
+ }
+
+ classCode += $@")]
+
+ public class {definition.Name} : IObjectBase<{definition.IdType}>
+
+ // Add Properties
+ {{
+ public {definition.IdType} Id {{ get; set;}}
+ ";
+
+ // Add all fields
+ var result1 = definition.Fields.Aggregate(string.Empty, (current, field) =>
+ current + $@" public {field.Type} {field.Name}{(field.Nullable ? "?" : "")} {{ get; set;}}");
+
+ // Complete class
+ classCode += result1;
+ classCode += @"} }";
+
+
+ classCode = FormatUsingRoslyn(classCode);
+
+ return CSharpSyntaxTree.ParseText(classCode);
+ }
+
+ public static string FormatUsingRoslyn(string csCode)
+ {
+ var tree = CSharpSyntaxTree.ParseText(csCode);
+ var root = tree.GetRoot()
+ .NormalizeWhitespace();
+ var result = root.ToFullString();
+ return result;
+ }
+
+}
diff --git a/src/TCDev.APIGenerator/Services/ODataScopeLookup.cs b/src/TCDev.APIGenerator/Services/ODataScopeLookup.cs
new file mode 100644
index 0000000..80f5de5
--- /dev/null
+++ b/src/TCDev.APIGenerator/Services/ODataScopeLookup.cs
@@ -0,0 +1,94 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data.Entity.Core.Metadata.Edm;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.OData.Query;
+using Microsoft.EntityFrameworkCore.Diagnostics;
+using Microsoft.OData.Edm;
+using Microsoft.OData.UriParser;
+using TCDev.ApiGenerator.Attributes;
+using TCDev.ApiGenerator.Interfaces;
+
+namespace TCDev.APIGenerator.Services
+{
+ public class ODataScopeLookup where T : IObjectBase
+ {
+ private readonly AssemblyService assemblyData;
+ public ODataScopeLookup(AssemblyService assemblyData)
+ {
+ this.assemblyData = assemblyData;
+ }
+
+ ///
+ /// Get all scopes required for the given query
+ /// walks along all $expand properties and checks all required scopes
+ /// returns a list of all scopes required
+ ///
+ ///
+ ///
+ public List GetRequestedScopes(ODataQueryOptions options)
+ {
+ var scopes = new List();
+ var types = GetRequestedTypes(options);
+ foreach (var type in types)
+ {
+ var attrs = Attribute.GetCustomAttributes(type);
+ if (attrs.FirstOrDefault(p => p.GetType() == typeof(ApiAttribute)) is not ApiAttribute optionAttrib)
+ continue;
+
+ if(optionAttrib.Options.RequiredReadScopes != null) scopes.AddRange(optionAttrib.Options.RequiredReadScopes);
+
+ }
+
+ return scopes;
+ }
+
+ ///
+ /// Get all types requested in the OData query,
+ /// walks along all $expand properties and returns all types requested
+ ///
+ ///
+ ///
+ public IEnumerable GetRequestedTypes(ODataQueryOptions options)
+ {
+ if (options.SelectExpand.GetType()
+ .GetProperty("ProcessedSelectExpandClause",
+ System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
+ ?.GetValue(options.SelectExpand) is SelectExpandClause selectedTypes)
+ {
+ var typesRequested = new List
+ {
+ this.assemblyData.Types.FirstOrDefault(p => p.Name == options.Context.NavigationSource.Name)
+ };
+ foreach (ExpandedNavigationSelectItem selectItem in selectedTypes.SelectedItems)
+ {
+ typesRequested.AddRange(GetTypes(selectItem).FindAll((x) => !typesRequested.Contains(x)));
+ }
+
+ return typesRequested;
+ }
+
+ return null;
+ }
+
+
+ private List GetTypes(ExpandedNavigationSelectItem item)
+ {
+ var types = new List
+ {
+ this.assemblyData.Types.FirstOrDefault(p => p.Name == item.NavigationSource.Name)
+ };
+
+ foreach (ExpandedNavigationSelectItem subItem in item.SelectAndExpand.SelectedItems)
+ {
+ types.AddRange(GetTypes(subItem).FindAll((x) => !types.Contains(x)));
+ }
+
+ return types;
+ }
+
+ }
+}
diff --git a/src/TCDev.APIGenerator/TCDev.APIGenerator.csproj b/src/TCDev.APIGenerator/TCDev.APIGenerator.csproj
index c5a54c4..4b274bd 100644
--- a/src/TCDev.APIGenerator/TCDev.APIGenerator.csproj
+++ b/src/TCDev.APIGenerator/TCDev.APIGenerator.csproj
@@ -3,11 +3,11 @@
net6.0
TCDev.APIGenerator
- 0.0.5-alpha
+ 0.1.2
Tim Cadenbach
TCDev
- Creates fully working CRUD Apis from just class files
- Debug;Release;DebugWithSampleApp;SampleAppNuget
+ Creates fully working CRUD Apis from just models
+ Debug;Release;DebugWithSampleApp;SampleAppNuget;SampleAppJson
@@ -18,6 +18,10 @@
TCDev.ApiGenerator.xml
+
+ TCDev.ApiGenerator.xml
+
+
@@ -33,12 +37,12 @@
+
-
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -57,5 +61,9 @@
+
+
+
+
diff --git a/src/TCDev.APIGenerator/TCDev.APIGenerator.xml b/src/TCDev.APIGenerator/TCDev.APIGenerator.xml
index 9e6423e..fee50e8 100644
--- a/src/TCDev.APIGenerator/TCDev.APIGenerator.xml
+++ b/src/TCDev.APIGenerator/TCDev.APIGenerator.xml
@@ -4,15 +4,13 @@
TCDev.APIGenerator
-
+
Attribute defining auto generated controller for the class
The full base route for the class ie /myclass/
-
-
-
-
+
+
@@ -24,12 +22,12 @@
Configuration settings for generated controller behaviour
-
+
Claims required for read access
-
+
Claims required for write access
@@ -54,34 +52,57 @@
Default cache duration
-
+
- Returns a list of entries
+ Returns a list of entries
-
+
- Generate EDM Model for OData functionalities
+ Generate EDM Model for OData functionality
-
+
- Enable Swagger in Production
+ Enable Swagger in Production
+
+
+ Get all scopes required for the given query
+ walks along all $expand properties and checks all required scopes
+ returns a list of all scopes required
+
+
+
+
+
+
+ Get all types requested in the OData query,
+ walks along all $expand properties and returns all types requested
+
+
+
+
Applies route conventions to allow routes for auto generated controllers
One of the main pieces to make the magic work.
-
+
Initiate the Controller generator
Names of assemblies to search for classes
+
+
+ Initiate the Controller generator
+
+ Names of assemblies to search for classes
+