diff --git a/crmcore.sln b/crmcore.sln index 4167c16..f9cb26e 100644 --- a/crmcore.sln +++ b/crmcore.sln @@ -76,9 +76,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CRMCore.Module.Data", "src\ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "base", "base", "{E71DEB4F-1656-46AA-979F-0211977DE983}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CRMCore.Module.Entities", "src\modules\base\CRMCore.Module.Entities\CRMCore.Module.Entities.csproj", "{A3971BCB-B342-4267-9783-D4C6A56A8391}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CRMCore.Module.Entities", "src\modules\base\CRMCore.Module.Entities\CRMCore.Module.Entities.csproj", "{A3971BCB-B342-4267-9783-D4C6A56A8391}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CRMCore.Module.MvcCore", "src\modules\base\CRMCore.Module.MvcCore\CRMCore.Module.MvcCore.csproj", "{AFA2A443-4642-4C86-A3EC-106122E22D5B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CRMCore.Module.MvcCore", "src\modules\base\CRMCore.Module.MvcCore\CRMCore.Module.MvcCore.csproj", "{AFA2A443-4642-4C86-A3EC-106122E22D5B}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CRMCore.Module.Data.SqlServer", "src\modules\support\CRMCore.Module.Data.SqlServer\CRMCore.Module.Data.SqlServer.csproj", "{C1668722-949E-495A-B6A9-12D49B810041}" EndProject @@ -86,6 +86,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CRMCore.Module.ReDoc", "src EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CRMCore.Module.Swagger", "src\modules\support\CRMCore.Module.Swagger\CRMCore.Module.Swagger.csproj", "{72596C0C-FDB0-40FB-90AC-01752A080118}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CRMCore.Module.GraphQL", "src\modules\crm\CRMCore.Module.GraphQL\CRMCore.Module.GraphQL.csproj", "{7ECB9B94-009E-4331-A924-5663C2111788}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -180,6 +182,10 @@ Global {72596C0C-FDB0-40FB-90AC-01752A080118}.Debug|Any CPU.Build.0 = Debug|Any CPU {72596C0C-FDB0-40FB-90AC-01752A080118}.Release|Any CPU.ActiveCfg = Release|Any CPU {72596C0C-FDB0-40FB-90AC-01752A080118}.Release|Any CPU.Build.0 = Release|Any CPU + {7ECB9B94-009E-4331-A924-5663C2111788}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7ECB9B94-009E-4331-A924-5663C2111788}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7ECB9B94-009E-4331-A924-5663C2111788}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7ECB9B94-009E-4331-A924-5663C2111788}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -212,6 +218,7 @@ Global {C1668722-949E-495A-B6A9-12D49B810041} = {18C4BB63-4369-4DF3-87D2-B203F0917F1E} {A9068043-B2DD-4E36-AF0D-AA5A918CFBEC} = {18C4BB63-4369-4DF3-87D2-B203F0917F1E} {72596C0C-FDB0-40FB-90AC-01752A080118} = {18C4BB63-4369-4DF3-87D2-B203F0917F1E} + {7ECB9B94-009E-4331-A924-5663C2111788} = {7C9ACFC1-00F0-4547-9AF4-1A0E43AE1935} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3171C67F-EFB1-46FF-A566-6DC6C1E80FC5} diff --git a/src/hosts/CRMCore.WebApp/ClientApp/src/components/Sidebar/_nav.js b/src/hosts/CRMCore.WebApp/ClientApp/src/components/Sidebar/_nav.js index 30911be..5de91cf 100644 --- a/src/hosts/CRMCore.WebApp/ClientApp/src/components/Sidebar/_nav.js +++ b/src/hosts/CRMCore.WebApp/ClientApp/src/components/Sidebar/_nav.js @@ -123,7 +123,7 @@ export default { }, { name: 'Swagger', - url: `${globalConfig.apiServer}/swagger`, + url: `${globalConfig.apiServer}/my-swagger`, icon: 'icon-star' }, { diff --git a/src/modules/crm/CRMCore.Module.Common/CRMCore.Module.Common.csproj b/src/modules/crm/CRMCore.Module.Common/CRMCore.Module.Common.csproj index 0831862..6941c44 100644 --- a/src/modules/crm/CRMCore.Module.Common/CRMCore.Module.Common.csproj +++ b/src/modules/crm/CRMCore.Module.Common/CRMCore.Module.Common.csproj @@ -2,7 +2,6 @@ netstandard2.0 - Library diff --git a/src/modules/crm/CRMCore.Module.Contact/CRMCore.Module.Contact.csproj b/src/modules/crm/CRMCore.Module.Contact/CRMCore.Module.Contact.csproj index d16480e..2deec8d 100644 --- a/src/modules/crm/CRMCore.Module.Contact/CRMCore.Module.Contact.csproj +++ b/src/modules/crm/CRMCore.Module.Contact/CRMCore.Module.Contact.csproj @@ -2,7 +2,6 @@ netstandard2.0 - Library diff --git a/src/modules/crm/CRMCore.Module.GraphQL/CRMCore.Module.GraphQL.csproj b/src/modules/crm/CRMCore.Module.GraphQL/CRMCore.Module.GraphQL.csproj new file mode 100644 index 0000000..a220072 --- /dev/null +++ b/src/modules/crm/CRMCore.Module.GraphQL/CRMCore.Module.GraphQL.csproj @@ -0,0 +1,17 @@ + + + + netstandard2.0 + Library + + + + + + + + + + + + diff --git a/src/modules/crm/CRMCore.Module.GraphQL/GraphQLController.cs b/src/modules/crm/CRMCore.Module.GraphQL/GraphQLController.cs new file mode 100644 index 0000000..8003d5f --- /dev/null +++ b/src/modules/crm/CRMCore.Module.GraphQL/GraphQLController.cs @@ -0,0 +1,35 @@ +using GraphQL; +using GraphQL.Http; +using GraphQL.Types; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; + +namespace CRMCore.Module.GraphQL +{ + [Area("CRMCore.Module.GraphQL")] + [Route("graphql/api/query")] + public class GraphQLController : Controller + { + private readonly Schema graphQLSchema; + + public GraphQLController(Schema schema) + { + graphQLSchema = schema; + } + + [HttpPost("")] + public async Task Get([FromQuery] string query = "{ crm_Tasks_list { id } }") + { + var result = await new DocumentExecuter().ExecuteAsync( + new ExecutionOptions() + { + Schema = graphQLSchema, + Query = query + } + ).ConfigureAwait(false); + + var json = new DocumentWriter(indent: true).Write(result.Data); + return json; + } + } +} diff --git a/src/modules/crm/CRMCore.Module.GraphQL/GraphQLQuery.cs b/src/modules/crm/CRMCore.Module.GraphQL/GraphQLQuery.cs new file mode 100644 index 0000000..368da9f --- /dev/null +++ b/src/modules/crm/CRMCore.Module.GraphQL/GraphQLQuery.cs @@ -0,0 +1,102 @@ +using CRMCore.Module.Data; +using CRMCore.Module.GraphQL.Models; +using GraphQL.Resolvers; +using GraphQL.Types; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; + +namespace CRMCore.Module.GraphQL +{ + public class GraphQLQuery : ObjectGraphType + { + private IDatabaseMetadata _dbMetadata; + private ApplicationDbContext _dbContext; + + public GraphQLQuery(ApplicationDbContext dbContext, IDatabaseMetadata dbMetadata) + { + _dbMetadata = dbMetadata; + _dbContext = dbContext; + + Name = "Query"; + + foreach (var metaTable in _dbMetadata.GetMetadataTables()) + { + var tableType = new TableType(metaTable); + /*AddField(new FieldType() + { + Name = metaTable.TableName, + Type = tableType.GetType(), + ResolvedType = tableType, + Resolver = new MyFieldResolver(metaTable, _dbContext), + Arguments = new QueryArguments( + tableType.TableArgs + ) + });*/ + + //lets add key to get list of current table + var listType = new ListGraphType(tableType); + AddField(new FieldType + { + Name = $"{metaTable.TableName}_list", + Type = listType.GetType(), + ResolvedType = listType, + Resolver = new MyFieldResolver(metaTable, _dbContext), + /*Arguments = new QueryArguments( + tableType.TableArgs + ) */ + }); + } + } + } + + public class MyFieldResolver : IFieldResolver + { + private TableMetadata _tableMetadata; + private ApplicationDbContext _dbContext; + + public MyFieldResolver(TableMetadata tableMetadata, ApplicationDbContext dbContext) + { + _tableMetadata = tableMetadata; + _dbContext = dbContext; + } + + public object Resolve(ResolveFieldContext context) + { + var source = context.Source; + + List> finalResult = new List>(); + if (context.FieldName.Contains("_list")) + { + + using (var command = _dbContext.Database.GetDbConnection().CreateCommand()) + { + command.CommandText = "SELECT * FROM " + _tableMetadata.TableName; + _dbContext.Database.OpenConnection(); + using (var result = command.ExecuteReader()) + { + while (result.Read()) + { + var temp = new Dictionary(); + + for (var index = 0; index < result.FieldCount; index++) + { + var lowerCase = Char.ToLowerInvariant(result.GetName(index)[0]) + result.GetName(index).Substring(1); + temp.Add(lowerCase, result[result.GetName(index)]); + } + + finalResult.Add(temp); + } + } + + if (_dbContext.Database.GetDbConnection().State == System.Data.ConnectionState.Open) + { + _dbContext.Database.CloseConnection(); + } + } + } + + return finalResult; + } + } +} diff --git a/src/modules/crm/CRMCore.Module.GraphQL/Models/ColumnMetadata.cs b/src/modules/crm/CRMCore.Module.GraphQL/Models/ColumnMetadata.cs new file mode 100644 index 0000000..9f6a4fe --- /dev/null +++ b/src/modules/crm/CRMCore.Module.GraphQL/Models/ColumnMetadata.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace CRMCore.Module.GraphQL.Models +{ + public class ColumnMetadata + { + [Column("name")] + public string ColumnName + { + get; set; + } + + [Column("type")] + public string DataType + { + get; set; + } + + [Column("notnull")] + public string IsNullable + { + get; set; + } + } +} diff --git a/src/modules/crm/CRMCore.Module.GraphQL/Models/DatabaseMetadata.cs b/src/modules/crm/CRMCore.Module.GraphQL/Models/DatabaseMetadata.cs new file mode 100644 index 0000000..9792742 --- /dev/null +++ b/src/modules/crm/CRMCore.Module.GraphQL/Models/DatabaseMetadata.cs @@ -0,0 +1,97 @@ +using CRMCore.Module.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using System.Collections.Generic; +using System.Linq; + +namespace CRMCore.Module.GraphQL.Models +{ + public class DatabaseMetadata : IDatabaseMetadata + { + protected ApplicationDbContext _dbContext; + + public DatabaseMetadata(ApplicationDbContext dbContext) + { + _dbContext = dbContext; + DatabaseName = _dbContext.Database.GetDbConnection().Database; + if (Tables == null) + LoadMetaData(); + } + + public string DatabaseName { get; set; } + + public List Tables { get; set; } + + public void ReloadMetadata() + { + LoadMetaData(); + } + + public IEnumerable GetMetadataTables() + { + if (Tables == null) + return new List(); + + return Tables; + } + + private void LoadMetaData() + { + // var res = new List(); + /*res.Add( + FetchTableMetaData("Customers") + );*/ + + Tables = FetchTableMetaData(); + } + + /*public List GetMetadataTables() + { + if (Tables == null) + return new List(); + + return Tables; + } */ + + private List FetchTableMetaData() + { + var metaTables = new List(); + foreach (var entityType in _dbContext.Model.GetEntityTypes()) + { + var metaTable = new TableMetadata(); + var relational = entityType.Relational(); + var tableName = relational.TableName; + + metaTable.TableName = tableName; + metaTable.Columns = GetColumnsMetadata(entityType).ToList(); + + metaTables.Add(metaTable); + } + + return metaTables; + } + + private IEnumerable GetColumnsMetadata(IEntityType entityType) + { + var tableColumns = new List(); + + foreach (var propertyType in entityType.GetProperties()) + { + var relational = propertyType.Relational(); + tableColumns.Add(new ColumnMetadata + { + ColumnName = relational.ColumnName, + DataType = relational.ColumnType + }); + } + + return tableColumns; + } + } + + public interface IDatabaseMetadata + { + void ReloadMetadata(); + IEnumerable GetMetadataTables(); + } +} diff --git a/src/modules/crm/CRMCore.Module.GraphQL/Models/TableMetadata.cs b/src/modules/crm/CRMCore.Module.GraphQL/Models/TableMetadata.cs new file mode 100644 index 0000000..bd54df7 --- /dev/null +++ b/src/modules/crm/CRMCore.Module.GraphQL/Models/TableMetadata.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; + +namespace CRMCore.Module.GraphQL.Models +{ + public class TableMetadata + { + [Column("table_name")] + public string TableName { get; set; } + + public List Columns { get; set; } + } +} diff --git a/src/modules/crm/CRMCore.Module.GraphQL/Module.txt b/src/modules/crm/CRMCore.Module.GraphQL/Module.txt new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/src/modules/crm/CRMCore.Module.GraphQL/Module.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/modules/crm/CRMCore.Module.GraphQL/ServiceCollectionExtensions.cs b/src/modules/crm/CRMCore.Module.GraphQL/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..0b24514 --- /dev/null +++ b/src/modules/crm/CRMCore.Module.GraphQL/ServiceCollectionExtensions.cs @@ -0,0 +1,25 @@ +using CRMCore.Module.Data; +using CRMCore.Module.GraphQL.Models; +using GraphQL.Types; +using Microsoft.Extensions.DependencyInjection; + +namespace CRMCore.Module.GraphQL +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddMyGraphQL(this IServiceCollection services) + { + services.AddScoped(); + services.AddScoped((resolver) => + { + var dbContext = resolver.GetRequiredService(); + var metaDatabase = resolver.GetRequiredService(); + var schema = new Schema { Query = new GraphQLQuery(dbContext, metaDatabase) }; + schema.Initialize(); + return schema; + }); + + return services; + } + } +} diff --git a/src/modules/crm/CRMCore.Module.GraphQL/TableType.cs b/src/modules/crm/CRMCore.Module.GraphQL/TableType.cs new file mode 100644 index 0000000..1ecdb5f --- /dev/null +++ b/src/modules/crm/CRMCore.Module.GraphQL/TableType.cs @@ -0,0 +1,108 @@ +using CRMCore.Module.GraphQL.Models; +using GraphQL; +using GraphQL.Resolvers; +using GraphQL.Types; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace CRMCore.Module.GraphQL +{ + public class TableType : ObjectGraphType> + { + public QueryArguments TableArgs + { + get; set; + } + + private IDictionary _SqliteTypeToSystemType; + protected IDictionary SqliteTypeToSystemType + { + get + { + if (_SqliteTypeToSystemType == null) + { + _SqliteTypeToSystemType = new Dictionary { + {"uniqueidentifier", typeof(String) }, + { "char", typeof(String) }, + { "nvarchar", typeof(String) }, + { "int", typeof(int) }, + { "decimal", typeof(decimal) }, + { "bit", typeof(bool) } + }; + } + return _SqliteTypeToSystemType; + } + } + + public TableType(TableMetadata tableMetadata) + { + Name = tableMetadata.TableName; + foreach (var tableColumn in tableMetadata.Columns) + { + InitGraphTableColumn(tableColumn); + } + } + + private void InitGraphTableColumn(ColumnMetadata columnMetadata) + { + var graphQLType = (ResolveColumnMetaType(columnMetadata.DataType)).GetGraphTypeFromType(true); + var columnField = Field( + graphQLType, + columnMetadata.ColumnName + ); + + columnField.Resolver = new DictionaryNameFieldResolver(); + // FillArgs(columnMetadata.ColumnName); + } + + /*private void FillArgs(string columnName) + { + if (TableArgs == null) + { + TableArgs = new QueryArguments( + new QueryArgument() + { + Name = columnName + } + ); + } + else + { + TableArgs.Add(new QueryArgument { Name = columnName }); + } + } */ + + private Type ResolveColumnMetaType(string dbType) + { + if (SqliteTypeToSystemType.ContainsKey(dbType)) + return SqliteTypeToSystemType[dbType]; + + return typeof(String); + } + } + + public class DictionaryNameFieldResolver : IFieldResolver + { + // private BindingFlags _flags = BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance; + + public object Resolve(ResolveFieldContext context) + { + var source = context.Source; + + if (source == null) + { + return null; + } + + var value = (source as IDictionary)[context.FieldAst.Name]; + + if (value == null) + { + throw new InvalidOperationException($"Expected to find property {context.FieldAst.Name} on {context.Source.GetType().Name} but it does not exist."); + } + + return value; + } + } +} diff --git a/src/targets/CRMCore.Application.Crm.targets/CRMCore.Application.Crm.targets.csproj b/src/targets/CRMCore.Application.Crm.targets/CRMCore.Application.Crm.targets.csproj index b35167c..edf03c5 100644 --- a/src/targets/CRMCore.Application.Crm.targets/CRMCore.Application.Crm.targets.csproj +++ b/src/targets/CRMCore.Application.Crm.targets/CRMCore.Application.Crm.targets.csproj @@ -15,6 +15,7 @@ + diff --git a/src/targets/CRMCore.Application.Crm.targets/ServiceExtensions.cs b/src/targets/CRMCore.Application.Crm.targets/ServiceExtensions.cs index eb79889..b0592f0 100644 --- a/src/targets/CRMCore.Application.Crm.targets/ServiceExtensions.cs +++ b/src/targets/CRMCore.Application.Crm.targets/ServiceExtensions.cs @@ -11,6 +11,7 @@ using System.Reflection; using System.Text.Encodings.Web; using CRMCore.Module.Swagger; +using CRMCore.Module.GraphQL; namespace CRMCore.Application.Crm.targets { @@ -45,6 +46,7 @@ public static IServiceCollection AddCrmCore(this IServiceCollection services) services.AddMvcModules(); services.AddMySwagger(); + services.AddMyGraphQL(); services.AddSpaStaticFiles(configuration => {