From 0dd65e7f9ac7069008effb957f507537e39e5601 Mon Sep 17 00:00:00 2001 From: exsersewo Date: Tue, 5 Jan 2021 07:22:28 +0000 Subject: [PATCH] Init Commit Works: -Databases --MySql/Mariadb --MongoDB --Postgres -Formatting -JSON -XML -HTML Todo: -Databases --SQLite --Firebird --- src/RestAPI/RestAPI.sln | 25 ++ src/RestAPI/RestAPI/.env.default | 7 + src/RestAPI/RestAPI/Clients/FirebirdClient.cs | 115 +++++++++ src/RestAPI/RestAPI/Clients/MongoDbClient.cs | 109 +++++++++ src/RestAPI/RestAPI/Clients/MySqlClient.cs | 114 +++++++++ src/RestAPI/RestAPI/Clients/NpgsqlClient.cs | 117 +++++++++ src/RestAPI/RestAPI/Clients/SQLiteClient.cs | 115 +++++++++ .../RestAPI/Controllers/TableController.cs | 227 ++++++++++++++++++ .../HttpResponseExceptionFilter.cs | 25 ++ .../Exceptions/HttpResponseException.cs | 23 ++ .../RestAPI/Interfaces/IDatabaseClient.cs | 18 ++ .../OutputFormatters/HtmlOutputFormatter.cs | 12 + src/RestAPI/RestAPI/Program.cs | 27 +++ .../RestAPI/Properties/launchSettings.json | 30 +++ src/RestAPI/RestAPI/RestAPI.csproj | 18 ++ src/RestAPI/RestAPI/Startup.cs | 205 ++++++++++++++++ .../RestAPI/appsettings.Development.json | 9 + src/RestAPI/RestAPI/appsettings.json | 10 + 18 files changed, 1206 insertions(+) create mode 100644 src/RestAPI/RestAPI.sln create mode 100644 src/RestAPI/RestAPI/.env.default create mode 100644 src/RestAPI/RestAPI/Clients/FirebirdClient.cs create mode 100644 src/RestAPI/RestAPI/Clients/MongoDbClient.cs create mode 100644 src/RestAPI/RestAPI/Clients/MySqlClient.cs create mode 100644 src/RestAPI/RestAPI/Clients/NpgsqlClient.cs create mode 100644 src/RestAPI/RestAPI/Clients/SQLiteClient.cs create mode 100644 src/RestAPI/RestAPI/Controllers/TableController.cs create mode 100644 src/RestAPI/RestAPI/ExceptionFilters/HttpResponseExceptionFilter.cs create mode 100644 src/RestAPI/RestAPI/Exceptions/HttpResponseException.cs create mode 100644 src/RestAPI/RestAPI/Interfaces/IDatabaseClient.cs create mode 100644 src/RestAPI/RestAPI/OutputFormatters/HtmlOutputFormatter.cs create mode 100644 src/RestAPI/RestAPI/Program.cs create mode 100644 src/RestAPI/RestAPI/Properties/launchSettings.json create mode 100644 src/RestAPI/RestAPI/RestAPI.csproj create mode 100644 src/RestAPI/RestAPI/Startup.cs create mode 100644 src/RestAPI/RestAPI/appsettings.Development.json create mode 100644 src/RestAPI/RestAPI/appsettings.json diff --git a/src/RestAPI/RestAPI.sln b/src/RestAPI/RestAPI.sln new file mode 100644 index 0000000..6803ca0 --- /dev/null +++ b/src/RestAPI/RestAPI.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30615.102 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestAPI", "RestAPI\RestAPI.csproj", "{3929B199-3AEB-41BA-993B-DA1AF2A73CA3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3929B199-3AEB-41BA-993B-DA1AF2A73CA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3929B199-3AEB-41BA-993B-DA1AF2A73CA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3929B199-3AEB-41BA-993B-DA1AF2A73CA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3929B199-3AEB-41BA-993B-DA1AF2A73CA3}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {972DCCF9-29C3-427D-82B0-1DA966964D24} + EndGlobalSection +EndGlobal diff --git a/src/RestAPI/RestAPI/.env.default b/src/RestAPI/RestAPI/.env.default new file mode 100644 index 0000000..15b040d --- /dev/null +++ b/src/RestAPI/RestAPI/.env.default @@ -0,0 +1,7 @@ +DATASOURCE= +HOST= +HOSTPORT= +USERNAME= +PASSWORD= +DATABASE= +BLACKLISTEDFIELDS= \ No newline at end of file diff --git a/src/RestAPI/RestAPI/Clients/FirebirdClient.cs b/src/RestAPI/RestAPI/Clients/FirebirdClient.cs new file mode 100644 index 0000000..8e6bd30 --- /dev/null +++ b/src/RestAPI/RestAPI/Clients/FirebirdClient.cs @@ -0,0 +1,115 @@ +using FirebirdSql.Data.FirebirdClient; +using RestAPI.Interfaces; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Threading.Tasks; + +namespace RestAPI.Clients +{ + public class FirebirdClient : IDatabaseClient + { + FbConnection connection; + IEnumerable Tables; + + public FirebirdClient(string connectionString) + { + connection = new FbConnection(connectionString); + } + + public async Task CloseAsync() + { + if (connection.State != System.Data.ConnectionState.Closed) + { + await connection.CloseAsync(); + } + } + + public async Task DoesTableExistAsync(string tableName) + { + if (Tables == null || !Tables.Any()) + { + await GetTablesAsync().ConfigureAwait(false); + } + + return Tables.Select(x => x.ToLowerInvariant()).Contains(tableName.ToLowerInvariant()); + } + + public async Task GetTableAsync(string tableName, string[] blacklistedFields = null) + { + if (blacklistedFields == null) + { + blacklistedFields = new string[0]; + } + + List>> content = null; + + FbCommand cmd = new FbCommand($"SELECT * FROM `{tableName}`", connection); + + var reader = await cmd.ExecuteReaderAsync(); + + if (reader.HasRows) + { + content = new List>>(); + + while (await reader.ReadAsync()) + { + var colSchema = reader.GetColumnSchema(); + + List> obj = new List>(); + + for (int col = 0; col < colSchema.Count; col++) + { + if (!blacklistedFields.Contains(colSchema[col].ColumnName.ToLowerInvariant())) + { + obj.Add( + new KeyValuePair( + colSchema[col].ColumnName, + Convert.ToString(reader[col]) + ) + ); + } + } + + content.Add(obj); + } + } + + await reader.CloseAsync(); + + return content; + } + + public async Task> GetTablesAsync() + { + if (Tables == null || !Tables.Any()) + { + List TableNames = new List(); + + FbCommand command = new FbCommand($"SHOW TABLES FROM `{connection.Database}`", connection); + using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + TableNames.Add(reader.GetString(0)); + } + + await reader.CloseAsync(); + } + + Tables = TableNames; + } + + return Tables; + } + + public async Task OpenAsync() + { + if (connection.State != System.Data.ConnectionState.Open) + { + await connection.OpenAsync(); + } + } + } +} diff --git a/src/RestAPI/RestAPI/Clients/MongoDbClient.cs b/src/RestAPI/RestAPI/Clients/MongoDbClient.cs new file mode 100644 index 0000000..dc4d789 --- /dev/null +++ b/src/RestAPI/RestAPI/Clients/MongoDbClient.cs @@ -0,0 +1,109 @@ +using Microsoft.AspNetCore.Routing; +using MongoDB.Driver; +using MongoDB.Driver.Core.Clusters; +using RestAPI.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace RestAPI.Clients +{ + public class MongoDbClient : IDatabaseClient + { + string database; + string connectionString; + MongoClient connection; + IEnumerable Tables; + + public MongoDbClient(string connectionString, string database) + { + this.connectionString = connectionString; + this.database = database; + connection = new MongoClient(connectionString); + } + + public Task CloseAsync() + { + connection.Cluster.Dispose(); + + return Task.CompletedTask; + } + + public async Task DoesTableExistAsync(string tableName) + { + if (Tables == null || !Tables.Any()) + { + await GetTablesAsync().ConfigureAwait(false); + } + + return Tables.Select(x => x.ToLowerInvariant()).Contains(tableName.ToLowerInvariant()); + } + + public async Task GetTableAsync(string tableName, string[] blacklistedFields = null) + { + if (blacklistedFields == null) + { + blacklistedFields = new string[0]; + } + + List>> content = null; + + var database = connection.GetDatabase(this.database); + + var collection = database.GetCollection(tableName); + + var entries = await (await collection.FindAsync(_ => true)).ToListAsync(); + + if (entries.Count > 0) + { + content = new List>>(); + + foreach (dynamic entry in entries) + { + var res = new RouteValueDictionary(entry); + + foreach (var blacklisted in blacklistedFields) + { + if (res.ContainsKey(blacklisted)) + { + res.Remove(blacklisted); + } + } + + List> obj = new List>(); + + foreach (var ent in res) + { + obj.Add(new KeyValuePair(ent.Key, Convert.ToString(ent.Value))); + } + + content.Add(obj); + } + } + + return content; + } + + public async Task> GetTablesAsync() + { + if (Tables == null || !Tables.Any()) + { + var database = connection.GetDatabase(this.database); + Tables = await (await database.ListCollectionNamesAsync()).ToListAsync(); + } + + return Tables; + } + + public Task OpenAsync() + { + if (connection.Cluster.Description.State != ClusterState.Connected) + { + connection = new MongoClient(connectionString); + } + + return Task.CompletedTask; + } + } +} diff --git a/src/RestAPI/RestAPI/Clients/MySqlClient.cs b/src/RestAPI/RestAPI/Clients/MySqlClient.cs new file mode 100644 index 0000000..39fb6cb --- /dev/null +++ b/src/RestAPI/RestAPI/Clients/MySqlClient.cs @@ -0,0 +1,114 @@ +using MySqlConnector; +using RestAPI.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace RestAPI.Clients +{ + public class MySqlClient : IDatabaseClient + { + MySqlConnection connection; + IEnumerable Tables; + + public MySqlClient(string connectionString) + { + connection = new MySqlConnection(connectionString); + } + + public async Task CloseAsync() + { + if (connection.State != System.Data.ConnectionState.Closed) + { + await connection.CloseAsync(); + } + } + + public async Task DoesTableExistAsync(string tableName) + { + if (Tables == null || !Tables.Any()) + { + await GetTablesAsync().ConfigureAwait(false); + } + + return Tables.Select(x => x.ToLowerInvariant()).Contains(tableName.ToLowerInvariant()); + } + + public async Task GetTableAsync(string tableName, string[] blacklistedFields = null) + { + if (blacklistedFields == null) + { + blacklistedFields = new string[0]; + } + + List>> content = null; + + MySqlCommand cmd = new MySqlCommand($"SELECT * FROM `{tableName}`", connection); + + var reader = await cmd.ExecuteReaderAsync(); + + if (reader.HasRows) + { + content = new List>>(); + + while (await reader.ReadAsync()) + { + var colSchema = await reader.GetColumnSchemaAsync(); + + List> obj = new List>(); + + for (int col = 0; col < colSchema.Count; col++) + { + if (!blacklistedFields.Contains(colSchema[col].ColumnName.ToLowerInvariant())) + { + obj.Add( + new KeyValuePair( + colSchema[col].ColumnName, + Convert.ToString(reader[col]) + ) + ); + } + } + + content.Add(obj); + } + } + + await reader.CloseAsync(); + + return content; + } + + public async Task> GetTablesAsync() + { + if (Tables == null || !Tables.Any()) + { + List TableNames = new List(); + + MySqlCommand command = new MySqlCommand($"SHOW TABLES FROM `{connection.Database}`", connection); + using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + TableNames.Add(reader.GetString(0)); + } + + await reader.CloseAsync(); + } + + Tables = TableNames; + } + + return Tables; + } + + public async Task OpenAsync() + { + if (connection.State != System.Data.ConnectionState.Open) + { + await connection.OpenAsync(); + } + } + } +} diff --git a/src/RestAPI/RestAPI/Clients/NpgsqlClient.cs b/src/RestAPI/RestAPI/Clients/NpgsqlClient.cs new file mode 100644 index 0000000..684c6e4 --- /dev/null +++ b/src/RestAPI/RestAPI/Clients/NpgsqlClient.cs @@ -0,0 +1,117 @@ +using Npgsql; +using RestAPI.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace RestAPI.Clients +{ + public class NpgsqlClient : IDatabaseClient + { + NpgsqlConnection connection; + IEnumerable Tables; + + public NpgsqlClient(string connectionString) + { + connection = new NpgsqlConnection(connectionString); + } + + public async Task CloseAsync() + { + if (connection.State != System.Data.ConnectionState.Closed) + { + await connection.CloseAsync(); + } + } + + public async Task DoesTableExistAsync(string tableName) + { + if (Tables == null || !Tables.Any()) + { + await GetTablesAsync().ConfigureAwait(false); + } + + return Tables.Select(x => x.ToLowerInvariant()).Contains(tableName.ToLowerInvariant()); + } + + public async Task GetTableAsync(string tableName, string[] blacklistedFields = null) + { + if (blacklistedFields == null) + { + blacklistedFields = new string[0]; + } + + List>> content = null; + + NpgsqlCommand cmd = new NpgsqlCommand($"SELECT * FROM {tableName}", connection); + + var reader = await cmd.ExecuteReaderAsync(); + + if (reader.HasRows) + { + content = new List>>(); + + while (await reader.ReadAsync()) + { + var colSchema = await reader.GetColumnSchemaAsync(); + + List> obj = new List>(); + + for (int col = 0; col < colSchema.Count; col++) + { + if (!blacklistedFields.Contains(colSchema[col].ColumnName.ToLowerInvariant())) + { + obj.Add( + new KeyValuePair( + colSchema[col].ColumnName, + Convert.ToString(reader[col]) + ) + ); + } + } + + content.Add(obj); + } + } + + await reader.CloseAsync(); + + return content; + } + + public async Task> GetTablesAsync() + { + if (Tables == null || !Tables.Any()) + { + List TableNames = new List(); + + NpgsqlCommand command = new NpgsqlCommand("SELECT * FROM information_schema.tables", connection); + using (var reader = await command.ExecuteReaderAsync()) + { + if (reader.HasRows) + { + while (await reader.ReadAsync()) + { + TableNames.Add(Convert.ToString($"{reader["table_schema"]}.{reader["table_name"]}")); + } + } + + await reader.CloseAsync(); + } + + Tables = TableNames; + } + + return Tables; + } + + public async Task OpenAsync() + { + if (connection.State != System.Data.ConnectionState.Open) + { + await connection.OpenAsync(); + } + } + } +} diff --git a/src/RestAPI/RestAPI/Clients/SQLiteClient.cs b/src/RestAPI/RestAPI/Clients/SQLiteClient.cs new file mode 100644 index 0000000..f2c639a --- /dev/null +++ b/src/RestAPI/RestAPI/Clients/SQLiteClient.cs @@ -0,0 +1,115 @@ +using Microsoft.Data.Sqlite; +using RestAPI.Interfaces; +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Threading.Tasks; + +namespace RestAPI.Clients +{ + public class SQLiteClient : IDatabaseClient + { + SqliteConnection connection; + IEnumerable Tables; + + public SQLiteClient(string connectionString) + { + connection = new SqliteConnection(connectionString); + } + + public async Task CloseAsync() + { + if (connection.State != System.Data.ConnectionState.Closed) + { + await connection.CloseAsync(); + } + } + + public async Task DoesTableExistAsync(string tableName) + { + if (Tables == null || !Tables.Any()) + { + await GetTablesAsync().ConfigureAwait(false); + } + + return Tables.Select(x => x.ToLowerInvariant()).Contains(tableName.ToLowerInvariant()); + } + + public async Task GetTableAsync(string tableName, string[] blacklistedFields = null) + { + if (blacklistedFields == null) + { + blacklistedFields = new string[0]; + } + + List>> content = null; + + SqliteCommand cmd = new SqliteCommand($"SELECT * FROM `{tableName}`", connection); + + var reader = await cmd.ExecuteReaderAsync(); + + if (reader.HasRows) + { + content = new List>>(); + + while (await reader.ReadAsync()) + { + var colSchema = reader.GetColumnSchema(); + + List> obj = new List>(); + + for (int col = 0; col < colSchema.Count; col++) + { + if (!blacklistedFields.Contains(colSchema[col].ColumnName.ToLowerInvariant())) + { + obj.Add( + new KeyValuePair( + colSchema[col].ColumnName, + Convert.ToString(reader[col]) + ) + ); + } + } + + content.Add(obj); + } + } + + await reader.CloseAsync(); + + return content; + } + + public async Task> GetTablesAsync() + { + if (Tables == null || !Tables.Any()) + { + List TableNames = new List(); + + SqliteCommand command = new SqliteCommand($"SHOW TABLES FROM `{connection.Database}`", connection); + using (var reader = await command.ExecuteReaderAsync()) + { + while (await reader.ReadAsync()) + { + TableNames.Add(reader.GetString(0)); + } + + await reader.CloseAsync(); + } + + Tables = TableNames; + } + + return Tables; + } + + public async Task OpenAsync() + { + if (connection.State != System.Data.ConnectionState.Open) + { + await connection.OpenAsync(); + } + } + } +} diff --git a/src/RestAPI/RestAPI/Controllers/TableController.cs b/src/RestAPI/RestAPI/Controllers/TableController.cs new file mode 100644 index 0000000..2702c36 --- /dev/null +++ b/src/RestAPI/RestAPI/Controllers/TableController.cs @@ -0,0 +1,227 @@ +using HtmlAgilityPack; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using RestAPI.Clients; +using RestAPI.Exceptions; +using RestAPI.Interfaces; +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Net; +using System.Xml; + +// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 + +namespace RestAPI.Controllers +{ + [Route("table")] + [ApiController] + public class TableController : ControllerBase + { + private readonly IDatabaseClient database; + private readonly IEnumerable BlacklistedFields; + + public TableController(IDatabaseClient database) + { + this.database = database; + + var fields = Environment.GetEnvironmentVariable("BLACKLISTEDFIELDS"); + + if (fields != null) + { + BlacklistedFields = fields.Split(",").Select(x => x.ToLowerInvariant()); + } + else + { + BlacklistedFields = new List(); + } + } + + private dynamic ConvertResponse(List>> tableContent, string format, HttpResponse Response) + { + switch (format.ToLowerInvariant()) + { + case "json": + { + Response.ContentType = "application/json"; + + List content = new List(); + + foreach (var result in tableContent) + { + dynamic item = new ExpandoObject(); + var dItem = item as IDictionary; + foreach (var attr in result) + { + dItem.Add(attr.Key, attr.Value); + } + + content.Add(item); + } + + return content; + } + case "xml": + { + Response.ContentType = "text/xml"; + var doc = new XmlDocument(); + + var results = doc.CreateNode(XmlNodeType.Element, null, "results", null); + + foreach (var result in tableContent) + { + var res = doc.CreateNode(XmlNodeType.Element, null, "result", null); + + foreach (var attr in result) + { + var resEl = doc.CreateNode(XmlNodeType.Element, null, attr.Key, null); + + resEl.InnerText = attr.Value; + + res.AppendChild(resEl); + } + + results.AppendChild(res); + + doc.AppendChild(results); + } + + return doc; + } + case "html": + { + Response.ContentType = "text/html"; + + var doc = new HtmlDocument(); + + var html = doc.CreateElement("html"); + var body = doc.CreateElement("body"); + var table = doc.CreateElement("table"); + var thead = doc.CreateElement("thead"); + var theadr = doc.CreateElement("tr"); + + doc.DocumentNode.AppendChild(html); + html.AppendChild(body); + body.AppendChild(table); + table.AppendChild(thead); + thead.AppendChild(theadr); + + foreach (var attr in tableContent[0]) + { + var th = doc.CreateElement("th"); + th.InnerHtml = attr.Key; + theadr.AppendChild(th); + } + + var tbody = doc.CreateElement("tbody"); + table.AppendChild(tbody); + + foreach (var result in tableContent) + { + var row = doc.CreateElement("tr"); + + foreach (var attr in result) + { + var resEl = doc.CreateElement("td"); + + resEl.InnerHtml = attr.Value; + + row.AppendChild(resEl); + } + + tbody.AppendChild(row); + } + + return doc.DocumentNode.InnerHtml; + } + default: + return null; + } + } + + [HttpGet] + public string Get() + { + throw new HttpResponseException(HttpStatusCode.BadRequest, "Must enter a valid table"); + } + + [HttpGet("{table}.{format}")] + public dynamic Get(string table, string format) + { + database.OpenAsync().GetAwaiter().GetResult(); + + string tableLocation = table; + + if (database is NpgsqlClient) + { + tableLocation = $"public.{table}"; + } + + if (database.DoesTableExistAsync(tableLocation).GetAwaiter().GetResult()) + { + var tableContent = database.GetTableAsync(tableLocation, BlacklistedFields.ToArray()).GetAwaiter().GetResult(); + dynamic response = null; + + if (tableContent != null) + { + response = ConvertResponse(tableContent as List>>, format, Response); + } + + if (response != null) + { + return response; + } + else + { + throw new HttpResponseException(HttpStatusCode.NotFound, $"Data within table \"{tableLocation}\" not found"); + } + } + else + { + throw new HttpResponseException(HttpStatusCode.NotFound, $"Table \"{tableLocation}\" not found"); + } + } + + [HttpGet("{area}/{table}.{format}")] + public dynamic Get(string area, string table, string format) + { + database.OpenAsync().GetAwaiter().GetResult(); + + string tableLocation; + + if (database is NpgsqlClient) + { + tableLocation = $"{area}.{table}"; + } + else + { + tableLocation = $"{area}/{table}"; //default assumption for now + } + + if (database.DoesTableExistAsync(tableLocation).GetAwaiter().GetResult()) + { + var tableContent = database.GetTableAsync(tableLocation, BlacklistedFields.ToArray()).GetAwaiter().GetResult(); + dynamic response = null; + + if (tableContent != null) + { + response = ConvertResponse(tableContent as List>>, format, Response); + } + + if (response != null) + { + return response; + } + else + { + throw new HttpResponseException(HttpStatusCode.NotFound, $"Data within table \"{tableLocation}\" not found"); + } + } + else + { + throw new HttpResponseException(HttpStatusCode.NotFound, $"Table \"{tableLocation}\" not found"); + } + } + } +} diff --git a/src/RestAPI/RestAPI/ExceptionFilters/HttpResponseExceptionFilter.cs b/src/RestAPI/RestAPI/ExceptionFilters/HttpResponseExceptionFilter.cs new file mode 100644 index 0000000..9e734ff --- /dev/null +++ b/src/RestAPI/RestAPI/ExceptionFilters/HttpResponseExceptionFilter.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using RestAPI.Exceptions; + +namespace RestAPI.ExceptionFilters +{ + public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter + { + public int Order { get; } = int.MaxValue - 10; + + public void OnActionExecuting(ActionExecutingContext context) { } + + public void OnActionExecuted(ActionExecutedContext context) + { + if (context.Exception is HttpResponseException exception) + { + context.Result = new ObjectResult(exception.Value) + { + StatusCode = exception.Status, + }; + context.ExceptionHandled = true; + } + } + } +} diff --git a/src/RestAPI/RestAPI/Exceptions/HttpResponseException.cs b/src/RestAPI/RestAPI/Exceptions/HttpResponseException.cs new file mode 100644 index 0000000..712cada --- /dev/null +++ b/src/RestAPI/RestAPI/Exceptions/HttpResponseException.cs @@ -0,0 +1,23 @@ +using System; +using System.Net; + +namespace RestAPI.Exceptions +{ + public class HttpResponseException : Exception + { + public int Status { get; set; } = 500; + + public object Value { get; set; } + + public HttpResponseException(int statusCode, object val) + { + Status = statusCode; + Value = val; + } + + public HttpResponseException(HttpStatusCode statusCode, object val) : this((int)statusCode, val) + { + + } + } +} diff --git a/src/RestAPI/RestAPI/Interfaces/IDatabaseClient.cs b/src/RestAPI/RestAPI/Interfaces/IDatabaseClient.cs new file mode 100644 index 0000000..bdbc946 --- /dev/null +++ b/src/RestAPI/RestAPI/Interfaces/IDatabaseClient.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace RestAPI.Interfaces +{ + public interface IDatabaseClient + { + public Task OpenAsync(); + + public Task CloseAsync(); + + public Task GetTableAsync(string tableName, string[] blacklistedFields = null); + + public Task DoesTableExistAsync(string tableName); + + public Task> GetTablesAsync(); + } +} diff --git a/src/RestAPI/RestAPI/OutputFormatters/HtmlOutputFormatter.cs b/src/RestAPI/RestAPI/OutputFormatters/HtmlOutputFormatter.cs new file mode 100644 index 0000000..2074691 --- /dev/null +++ b/src/RestAPI/RestAPI/OutputFormatters/HtmlOutputFormatter.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace RestAPI.OutputFormatters +{ + public class HtmlOutputFormatter : StringOutputFormatter + { + public HtmlOutputFormatter() + { + SupportedMediaTypes.Add("text/html"); + } + } +} diff --git a/src/RestAPI/RestAPI/Program.cs b/src/RestAPI/RestAPI/Program.cs new file mode 100644 index 0000000..71c4b39 --- /dev/null +++ b/src/RestAPI/RestAPI/Program.cs @@ -0,0 +1,27 @@ +using dotenv.net; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using System; +using System.IO; + +namespace RestAPI +{ + public class Program + { + public static void Main(string[] args) + { + string envFile = Path.Combine(AppContext.BaseDirectory, ".env"); + + DotEnv.Config(false, filePath: envFile); + + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/src/RestAPI/RestAPI/Properties/launchSettings.json b/src/RestAPI/RestAPI/Properties/launchSettings.json new file mode 100644 index 0000000..f7d3b2d --- /dev/null +++ b/src/RestAPI/RestAPI/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iis": { + "applicationUrl": "http://localhost/RestAPI", + "sslPort": 0 + }, + "iisExpress": { + "applicationUrl": "http://localhost:35486", + "sslPort": 44381 + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "IIS Express": { + "commandName": "IIS", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "RestAPI": { + "commandName": "Project", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://0.0.0.0:5001;http://0.0.0.0:5000" + } + } +} \ No newline at end of file diff --git a/src/RestAPI/RestAPI/RestAPI.csproj b/src/RestAPI/RestAPI/RestAPI.csproj new file mode 100644 index 0000000..748f6a5 --- /dev/null +++ b/src/RestAPI/RestAPI/RestAPI.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + + + diff --git a/src/RestAPI/RestAPI/Startup.cs b/src/RestAPI/RestAPI/Startup.cs new file mode 100644 index 0000000..be1a045 --- /dev/null +++ b/src/RestAPI/RestAPI/Startup.cs @@ -0,0 +1,205 @@ +using FirebirdSql.Data.FirebirdClient; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using MongoDB.Driver; +using MySqlConnector; +using Npgsql; +using RestAPI.Clients; +using RestAPI.ExceptionFilters; +using RestAPI.Interfaces; +using RestAPI.OutputFormatters; +using System; +using System.IO; +using System.Linq; + +namespace RestAPI +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + string dataSource = Environment.GetEnvironmentVariable("DATASOURCE"); + + if (dataSource != null) + { + string serverHost = Environment.GetEnvironmentVariable("HOST"); + string serverHostPort = Environment.GetEnvironmentVariable("HOSTPORT"); + string username = Environment.GetEnvironmentVariable("USERNAME"); + string password = Environment.GetEnvironmentVariable("PASSWORD"); + string database = Environment.GetEnvironmentVariable("DATABASE"); + + ushort hostPort = 0; + + if (!ushort.TryParse(serverHostPort, out hostPort)) + { + + } + + switch (dataSource.ToLowerInvariant()) + { + case "mysql": + case "mariadb": + { + if (hostPort == 0) + { + hostPort = 3306; + } + + services.AddSingleton(new MySqlClient(new MySqlConnectionStringBuilder + { + Server = serverHost, + Port = hostPort, + UserID = username, + Password = password, + Database = database, + AllowUserVariables = true + }.ConnectionString)); + } + break; + case "mongo": + case "mongodb": + { + if (hostPort == 0) + { + hostPort = 27017; + } + + MongoClientSettings builder = new MongoClientSettings(); + + string prefix = "mongodb"; + + if (!System.Net.IPAddress.TryParse(serverHost, out _)) + { + prefix += "+srv"; + } + + string connString; + + if (!string.IsNullOrEmpty(username)) + { + connString = string.Format( + "{0}://{1}:{2}@{3}:{4}/", + prefix, + username, + password, + serverHost, + hostPort + ); + } + else + { + connString = string.Format( + "{0}://{1}:{2}/", + prefix, + serverHost, + hostPort + ); + } + + services.AddSingleton(new MongoDbClient(connString, database)); + } + break; + case "firebird": + case "interbase": + { + //TODO: TEST FIREBASE + throw new NotImplementedException(); + + if (hostPort == 0) + { + hostPort = 3050; + } + + services.AddSingleton(new FirebirdClient(new FbConnectionStringBuilder + { + DataSource = serverHost, + Port = hostPort, + UserID = username, + Password = password, + ServerType = FbServerType.Default + }.ConnectionString)); + } + break; + case "pgsql": + case "postgresql": + { + if (hostPort == 0) + { + hostPort = 5432; + } + + services.AddSingleton(new NpgsqlClient(new NpgsqlConnectionStringBuilder + { + Host = serverHost, + Port = hostPort, + Database = database, + Username = username, + Password = password + }.ConnectionString)); + } + break; + case "sqlite": + { + //TODO: TEST SQLite + throw new NotImplementedException(); + + SqliteConnectionStringBuilder builder = new SqliteConnectionStringBuilder(); + + var sqliteFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.sqlite", SearchOption.AllDirectories); + + if (File.Exists(database)) + { + builder.DataSource = database; + } + else if (!database.Contains(".sqlite")) + { + builder.DataSource = sqliteFiles.FirstOrDefault(x => x.Contains($"{database}.sqlite")); + } + + services.AddSingleton(new SQLiteClient(builder.ConnectionString)); + } + break; + } + } + + services.AddControllers(options => + { + options.Filters.Add(new HttpResponseExceptionFilter()); + options.OutputFormatters.Add(new HtmlOutputFormatter()); + }) + .AddXmlSerializerFormatters(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/src/RestAPI/RestAPI/appsettings.Development.json b/src/RestAPI/RestAPI/appsettings.Development.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/src/RestAPI/RestAPI/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/RestAPI/RestAPI/appsettings.json b/src/RestAPI/RestAPI/appsettings.json new file mode 100644 index 0000000..d9d9a9b --- /dev/null +++ b/src/RestAPI/RestAPI/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +}