diff --git a/init.sql b/Database/init.sql similarity index 85% rename from init.sql rename to Database/init.sql index 634dc8e..62bfe65 100644 --- a/init.sql +++ b/Database/init.sql @@ -1,4 +1,4 @@ -create table address +create table if not exists address ( id serial not null constraint address_pk @@ -18,10 +18,10 @@ create table address country text not null ); -create unique index address_id_uindex +create unique index if not exists address_id_uindex on address (id); -create table offer +create table if not exists offer ( id serial not null constraint offer_pkey @@ -40,7 +40,7 @@ create table offer region text not null ); -create table consumable +create table if not exists consumable ( id serial not null constraint consumables_pk @@ -59,10 +59,10 @@ create table consumable is_deleted boolean default false not null ); -create unique index consumables_id_uindex +create unique index if not exists consumables_id_uindex on consumable (id); -create table device +create table if not exists device ( category text not null, name text not null, @@ -80,10 +80,10 @@ create table device is_deleted boolean default false not null ); -create unique index device_id_uindex +create unique index if not exists device_id_uindex on device (id); -create table personal +create table if not exists personal ( id serial not null constraint manpower_pk @@ -101,10 +101,10 @@ create table personal is_deleted boolean default false not null ); -create unique index manpower_id_uindex +create unique index if not exists manpower_id_uindex on personal (id); -create table region_subscription +create table if not exists region_subscription ( id serial not null constraint region_subscription_pk @@ -120,7 +120,7 @@ create table region_subscription region text not null ); -create table change +create table if not exists change ( id serial not null constraint change_pk @@ -136,7 +136,7 @@ create table change region text not null ); -create table demand +create table if not exists demand ( id serial not null constraint demand_pk @@ -153,10 +153,10 @@ create table demand created_at_timestamp timestamp not null ); -create index demand_token_index +create index if not exists demand_token_index on demand (token); -create table demand_device +create table if not exists demand_device ( id serial not null constraint demand_device_pk @@ -174,7 +174,7 @@ create table demand_device is_deleted boolean not null ); -create table demand_consumable +create table if not exists demand_consumable ( id serial not null constraint demand_consumable_pk diff --git a/Database/init_dummy_offers.json b/Database/init_dummy_offers.json new file mode 100644 index 0000000..e48bf04 --- /dev/null +++ b/Database/init_dummy_offers.json @@ -0,0 +1,53 @@ +{ + "OfferContexts": [ + { + "Address": { + "hascoordinates": true, + "latitude": "48.13756", + "longitude": "11.57490", + "is_deleted": false, + "street_line_1": "Marienplatz 1", + "street_line_2": null, + "street_line_3": null, + "street_line_4": null, + "county": null, + "city": "München", + "state": null, + "postal_code": "80331", + "country": "DEUTSCHLAND" + }, + "Offer": { + "name": "GermanDummyPirat", + "mail": "pirat.hilfsmittel@gmail.com", + "phone": "123456", + "organisation": "DummyOrganisation", + "ispublic": false, + "token": "0P2uADVO3Y7Q9YstM7Z08vfjFRhUQE", + "timestamp":"2020-05-12 00:00:00.000000", + "region": "de" + }, + "Consumables": [ + { + "category": "MASKE", + "name": "FFP2 MASKE", + "manufacturer": "German Dummy & Co", + "ordernumber": 545454, + "amount": 100, + "unit": "Packung", + "annotation": "Nur ein Dummy", + "is_deleted": false + }, + { + "category": "MASKE", + "name": "FFP3 MASKE", + "manufacturer": "German Dummy & Co", + "ordernumber": 77777, + "amount": 50, + "unit": "Packung", + "annotation": "Nur ein Dummy", + "is_deleted": false + } + ] + } + ] +} \ No newline at end of file diff --git a/Pirat/DatabaseContext/DatabaseInitialization.cs b/Pirat/DatabaseContext/DatabaseInitialization.cs new file mode 100644 index 0000000..00d7fe6 --- /dev/null +++ b/Pirat/DatabaseContext/DatabaseInitialization.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using Npgsql; +using Pirat.Model.Entity.Resource.Common; +using Pirat.Model.Entity.Resource.Stock; + +namespace Pirat.DatabaseContext +{ + + /// + /// The related tables if an offer is inserted to the database + /// + public class OfferContext + { + [JsonProperty] + public AddressEntity Address { get; set; } + + [JsonProperty] + public OfferEntity Offer { get; set; } + + [JsonProperty] + public List Consumables { get; set; } + + [JsonProperty] + public List Devices { get; set; } + + [JsonProperty] + public List Personals { get; set; } + } + + public class OffersInitialization + { + [JsonProperty] + public List OfferContexts { get; set; } + } + + public static class DatabaseInitialization + { + + private static readonly string DatabaseInitializationFilesLocation = + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Database"); + + private static readonly string DatabaseTableInitializationFile = + Path.Combine(DatabaseInitializationFilesLocation, "init.sql"); + + /// + /// Checks if connection to database can be established. Otherwise exception is thrown. + /// + public static void CheckConnectionToDatabase() + { + var connection = new NpgsqlConnection(Environment.GetEnvironmentVariable("PIRAT_CONNECTION")); + connection.Open(); + connection.Dispose(); + } + + /// + /// Create new tables in database if not exist so far. Table create commands are read from init.sql. + /// + public static void InitDatabaseTables() + { + CheckConnectionToDatabase(); + + var commandsInput = File.ReadAllText(DatabaseTableInitializationFile, Encoding.UTF8); + + var commands = commandsInput.Split(';'); + + var tablesBefore = FindExistingTables(); + Console.Out.WriteLine($"Tables in database:"); + foreach (var table in tablesBefore) + { + Console.Out.WriteLine($"{table}"); + } + + using (NpgsqlConnection connection = + new NpgsqlConnection(Environment.GetEnvironmentVariable("PIRAT_CONNECTION"))) + { + connection.Open(); + + foreach (var command in commands) + { + using NpgsqlCommand sqlCommand = new NpgsqlCommand(command + ";", connection); + sqlCommand.ExecuteNonQuery(); + } + } + + var tablesAfter = FindExistingTables(); + foreach (var table in tablesAfter.Except(tablesBefore)) + { + Console.Out.WriteLine($"Created table: {table}"); + } + } + + /// + /// The database that is found by using the connection string is initialized with dummy data. + /// Dummy data will only be inserted if associated tables are empty. + /// + public static async void InitDatabaseWithDummyData() + { + CheckConnectionToDatabase(); + + DbContextOptions options = + new DbContextOptionsBuilder().UseNpgsql(Environment.GetEnvironmentVariable("PIRAT_CONNECTION")).Options; + + await using var context = new ResourceContext(options); + //Insert only dummy values if offer table empty + if (!context.offer.Any()) + { + var dummyData = Path.Combine(DatabaseInitializationFilesLocation, "init_dummy_offers.json"); + var content = File.ReadAllText(dummyData); + var offerInit = JsonConvert.DeserializeObject(content); + foreach (var offerContext in offerInit.OfferContexts) + { + var address = (AddressEntity) await offerContext.Address.InsertAsync(context); + offerContext.Offer.address_id = address.Id; + var offer = (OfferEntity) await offerContext.Offer.InsertAsync(context); + + if (offerContext.Consumables != null) + { + foreach (var consumable in offerContext.Consumables) + { + consumable.offer_id = offer.id; + await consumable.InsertAsync(context); + } + } + + if (offerContext.Devices != null) + { + foreach (var device in offerContext.Devices) + { + device.offer_id = offer.id; + await device.InsertAsync(context); + } + } + + if (offerContext.Personals != null) + { + foreach (var personal in offerContext.Personals) + { + personal.offer_id = offer.id; + await personal.InsertAsync(context); + } + } + } + } + //Insert other dummy data contexts here + } + + private static List FindExistingTables() + { + var existingTables = new List(); + using (NpgsqlConnection connection = + new NpgsqlConnection(Environment.GetEnvironmentVariable("PIRAT_CONNECTION"))) + { + connection.Open(); + using NpgsqlCommand c = new NpgsqlCommand("SELECT table_name " + + "FROM information_schema.tables " + + "WHERE table_schema = 'public' " + + "AND table_type = 'BASE TABLE';", connection); + + using NpgsqlDataReader rdr = c.ExecuteReader(); + while (rdr.Read()) + { + existingTables.Add(rdr.GetString(0)); + } + } + + return existingTables; + } + } +} diff --git a/Pirat/Pirat.csproj b/Pirat/Pirat.csproj index 3de2576..17917f6 100644 --- a/Pirat/Pirat.csproj +++ b/Pirat/Pirat.csproj @@ -37,6 +37,11 @@ + + + + + @@ -67,4 +72,10 @@ $(NoWarn);1591 + + + Always + + + diff --git a/Pirat/Program.cs b/Pirat/Program.cs index 015c4da..658369b 100644 --- a/Pirat/Program.cs +++ b/Pirat/Program.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -using Npgsql; +using Pirat.DatabaseContext; namespace Pirat { @@ -13,7 +13,13 @@ public class Program public static void Main(string[] args) { CheckEnvironmentVariables(); - CheckConnectionToDatabase(); + + DatabaseInitialization.CheckConnectionToDatabase(); + var initTables = string.Equals(Environment.GetEnvironmentVariable("PIRAT_INIT_DB_TABLES_IF_NOT_EXIST"), "true", StringComparison.Ordinal); + var initData = string.Equals(Environment.GetEnvironmentVariable("PIRAT_INIT_DUMMY_DATA_IF_NOT_EXIST"),"true", StringComparison.Ordinal); + if (initTables) DatabaseInitialization.InitDatabaseTables(); + if (initData) DatabaseInitialization.InitDatabaseWithDummyData(); + CreateHostBuilder(args).Build().Run(); } @@ -35,7 +41,10 @@ public static void CheckEnvironmentVariables() "PIRAT_ADMIN_KEY", "PIRAT_SWAGGER_PREFIX_PATH", - "PIRAT_WEBAPP_ENVIRONMENT" + "PIRAT_WEBAPP_ENVIRONMENT", + + "PIRAT_INIT_DB_TABLES_IF_NOT_EXIST", + "PIRAT_INIT_DUMMY_DATA_IF_NOT_EXIST" }; foreach (var requiredEnvironmentVariable in requiredEnvironmentVariables) { @@ -46,14 +55,6 @@ public static void CheckEnvironmentVariables() } } - public static void CheckConnectionToDatabase() - { - var connection = new NpgsqlConnection(Environment.GetEnvironmentVariable("PIRAT_CONNECTION")); - connection.Open(); - connection.Dispose(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => diff --git a/README.md b/README.md index 23654bc..d95193f 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,9 @@ See https://dotnet.microsoft.com/download/dotnet-core/3.1 to get the SDK #### Database -This program uses a PostgreSQL database. The database definition is in `init.sql`. +This program uses a PostgreSQL database. The database definition is in `Database/init.sql`. + +Dummy data that is possible loaded into the database (see section Environment Variables) is in `Database`. #### Compile @@ -57,6 +59,11 @@ The following environment variables are available, most of them are required. * PIRAT_GOOGLE_API_KEY * PIRAT_GOOGLE_RECAPTCHA_SECRET +**Database Initialization** + +* PIRAT_INIT_DB_TABLES_IF_NOT_EXIST - If set to `true` tables that do not exist so far are added to the schema. +* PIRAT_INIT_DUMMY_DATA_IF_NOT_EXIST - Insert predefined dummy data into the tables if set to `true`. If tables are not empty, the dummy data gets not inserted. + Example: @@ -77,6 +84,9 @@ PIRAT_INTERNAL_RECEIVER_MAIL=mail@pirat-tool.com PIRAT_GOOGLE_API_KEY=KKKKEEEEYYYYY PIRAT_GOOGLE_RECAPTCHA_SECRET=SSSSEEECCCRRREEEEEETTTTTTT + +PIRAT_INIT_DB_TABLES_IF_NOT_EXIST=true +PIRAT_INIT_DUMMY_DATA_IF_NOT_EXIST=true ``` diff --git a/test-database_docker-compose.yml b/test-database_docker-compose.yml index 0c0d80d..3e54886 100644 --- a/test-database_docker-compose.yml +++ b/test-database_docker-compose.yml @@ -11,4 +11,4 @@ services: ports: - 5432:5432 volumes: - - ./init.sql:/docker-entrypoint-initdb.d/init.sql + - ./Database/init.sql:/docker-entrypoint-initdb.d/init.sql