From f8d96ec40f3794bce4939834e5294c73b55fa53e Mon Sep 17 00:00:00 2001 From: Anton Martynenko Date: Tue, 28 Oct 2025 11:07:07 +0100 Subject: [PATCH] database and migrations --- SatelliteProvider.Api/Program.cs | 20 +++ .../SatelliteProvider.Api.csproj | 1 + .../appsettings.Development.json | 11 ++ SatelliteProvider.Api/appsettings.json | 18 ++- .../Configs/DatabaseConfig.cs | 7 + .../Configs/ProcessingConfig.cs | 9 ++ .../Configs/StorageConfig.cs | 8 ++ .../DatabaseMigrator.cs | 43 ++++++ .../Migrations/001_CreateTilesTable.sql | 14 ++ .../Migrations/002_CreateRegionsTable.sql | 15 +++ .../Migrations/003_CreateIndexes.sql | 4 + .../Models/RegionEntity.cs | 18 +++ .../Models/TileEntity.cs | 17 +++ .../Repositories/IRegionRepository.cs | 13 ++ .../Repositories/ITileRepository.cs | 14 ++ .../Repositories/RegionRepository.cs | 89 +++++++++++++ .../Repositories/TileRepository.cs | 123 ++++++++++++++++++ .../SatelliteProvider.DataAccess.csproj | 21 +++ SatelliteProvider.sln | 6 + docker-compose.yml | 21 +++ 20 files changed, 471 insertions(+), 1 deletion(-) create mode 100644 SatelliteProvider.Common/Configs/DatabaseConfig.cs create mode 100644 SatelliteProvider.Common/Configs/ProcessingConfig.cs create mode 100644 SatelliteProvider.Common/Configs/StorageConfig.cs create mode 100644 SatelliteProvider.DataAccess/DatabaseMigrator.cs create mode 100644 SatelliteProvider.DataAccess/Migrations/001_CreateTilesTable.sql create mode 100644 SatelliteProvider.DataAccess/Migrations/002_CreateRegionsTable.sql create mode 100644 SatelliteProvider.DataAccess/Migrations/003_CreateIndexes.sql create mode 100644 SatelliteProvider.DataAccess/Models/RegionEntity.cs create mode 100644 SatelliteProvider.DataAccess/Models/TileEntity.cs create mode 100644 SatelliteProvider.DataAccess/Repositories/IRegionRepository.cs create mode 100644 SatelliteProvider.DataAccess/Repositories/ITileRepository.cs create mode 100644 SatelliteProvider.DataAccess/Repositories/RegionRepository.cs create mode 100644 SatelliteProvider.DataAccess/Repositories/TileRepository.cs create mode 100644 SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj create mode 100644 docker-compose.yml diff --git a/SatelliteProvider.Api/Program.cs b/SatelliteProvider.Api/Program.cs index 5095f12..12d8825 100644 --- a/SatelliteProvider.Api/Program.cs +++ b/SatelliteProvider.Api/Program.cs @@ -2,9 +2,22 @@ using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; +using SatelliteProvider.DataAccess; +using SatelliteProvider.DataAccess.Repositories; +using SatelliteProvider.Common.Configs; var builder = WebApplication.CreateBuilder(args); +var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") + ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); + +builder.Services.Configure(builder.Configuration.GetSection("MapConfig")); +builder.Services.Configure(builder.Configuration.GetSection("StorageConfig")); +builder.Services.Configure(builder.Configuration.GetSection("ProcessingConfig")); + +builder.Services.AddSingleton(sp => new TileRepository(connectionString)); +builder.Services.AddSingleton(sp => new RegionRepository(connectionString)); + builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { @@ -32,6 +45,13 @@ builder.Services.AddSwaggerGen(c => var app = builder.Build(); +var logger = app.Services.GetRequiredService>(); +var migrator = new DatabaseMigrator(connectionString, logger as ILogger); +if (!migrator.RunMigrations()) +{ + throw new Exception("Database migration failed. Application cannot start."); +} + if (app.Environment.IsDevelopment()) { app.UseSwagger(); diff --git a/SatelliteProvider.Api/SatelliteProvider.Api.csproj b/SatelliteProvider.Api/SatelliteProvider.Api.csproj index d329bc4..c67e5b4 100644 --- a/SatelliteProvider.Api/SatelliteProvider.Api.csproj +++ b/SatelliteProvider.Api/SatelliteProvider.Api.csproj @@ -15,6 +15,7 @@ + diff --git a/SatelliteProvider.Api/appsettings.Development.json b/SatelliteProvider.Api/appsettings.Development.json index 0c208ae..4d732cc 100644 --- a/SatelliteProvider.Api/appsettings.Development.json +++ b/SatelliteProvider.Api/appsettings.Development.json @@ -4,5 +4,16 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Port=5432;Database=satelliteprovider;Username=postgres;Password=postgres" + }, + "MapConfig": { + "Service": "GoogleMaps", + "ApiKey": "YOUR_API_KEY_HERE" + }, + "StorageConfig": { + "TilesDirectory": "./tiles", + "ReadyDirectory": "./ready" } } diff --git a/SatelliteProvider.Api/appsettings.json b/SatelliteProvider.Api/appsettings.json index 10f68b8..43a400a 100644 --- a/SatelliteProvider.Api/appsettings.json +++ b/SatelliteProvider.Api/appsettings.json @@ -5,5 +5,21 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Database=satelliteprovider;Username=postgres;Password=postgres" + }, + "MapConfig": { + "Service": "GoogleMaps", + "ApiKey": "YOUR_API_KEY_HERE" + }, + "StorageConfig": { + "TilesDirectory": "./tiles", + "ReadyDirectory": "./ready" + }, + "ProcessingConfig": { + "MaxConcurrentDownloads": 4, + "DefaultZoomLevel": 20, + "QueueCapacity": 100 + } } diff --git a/SatelliteProvider.Common/Configs/DatabaseConfig.cs b/SatelliteProvider.Common/Configs/DatabaseConfig.cs new file mode 100644 index 0000000..fc6d633 --- /dev/null +++ b/SatelliteProvider.Common/Configs/DatabaseConfig.cs @@ -0,0 +1,7 @@ +namespace SatelliteProvider.Common.Configs; + +public class DatabaseConfig +{ + public string ConnectionString { get; set; } = null!; +} + diff --git a/SatelliteProvider.Common/Configs/ProcessingConfig.cs b/SatelliteProvider.Common/Configs/ProcessingConfig.cs new file mode 100644 index 0000000..b8806c5 --- /dev/null +++ b/SatelliteProvider.Common/Configs/ProcessingConfig.cs @@ -0,0 +1,9 @@ +namespace SatelliteProvider.Common.Configs; + +public class ProcessingConfig +{ + public int MaxConcurrentDownloads { get; set; } = 4; + public int DefaultZoomLevel { get; set; } = 20; + public int QueueCapacity { get; set; } = 100; +} + diff --git a/SatelliteProvider.Common/Configs/StorageConfig.cs b/SatelliteProvider.Common/Configs/StorageConfig.cs new file mode 100644 index 0000000..bac4ad4 --- /dev/null +++ b/SatelliteProvider.Common/Configs/StorageConfig.cs @@ -0,0 +1,8 @@ +namespace SatelliteProvider.Common.Configs; + +public class StorageConfig +{ + public string TilesDirectory { get; set; } = "/tiles"; + public string ReadyDirectory { get; set; } = "/ready"; +} + diff --git a/SatelliteProvider.DataAccess/DatabaseMigrator.cs b/SatelliteProvider.DataAccess/DatabaseMigrator.cs new file mode 100644 index 0000000..9bf82d0 --- /dev/null +++ b/SatelliteProvider.DataAccess/DatabaseMigrator.cs @@ -0,0 +1,43 @@ +using System.Reflection; +using DbUp; +using Microsoft.Extensions.Logging; + +namespace SatelliteProvider.DataAccess; + +public class DatabaseMigrator +{ + private readonly string _connectionString; + private readonly ILogger? _logger; + + public DatabaseMigrator(string connectionString, ILogger? logger = null) + { + _connectionString = connectionString; + _logger = logger; + } + + public bool RunMigrations() + { + _logger?.LogInformation("Starting database migrations..."); + + EnsureDatabase.For.PostgresqlDatabase(_connectionString); + + var upgrader = DeployChanges.To + .PostgresqlDatabase(_connectionString) + .WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly(), + script => script.Contains(".Migrations.")) + .LogToConsole() + .Build(); + + var result = upgrader.PerformUpgrade(); + + if (!result.Successful) + { + _logger?.LogError(result.Error, "Database migration failed"); + return false; + } + + _logger?.LogInformation("Database migrations completed successfully"); + return true; + } +} + diff --git a/SatelliteProvider.DataAccess/Migrations/001_CreateTilesTable.sql b/SatelliteProvider.DataAccess/Migrations/001_CreateTilesTable.sql new file mode 100644 index 0000000..d46f982 --- /dev/null +++ b/SatelliteProvider.DataAccess/Migrations/001_CreateTilesTable.sql @@ -0,0 +1,14 @@ +CREATE TABLE tiles ( + id UUID PRIMARY KEY, + zoom_level INT NOT NULL, + latitude DOUBLE PRECISION NOT NULL, + longitude DOUBLE PRECISION NOT NULL, + tile_size_meters DOUBLE PRECISION NOT NULL, + tile_size_pixels INT NOT NULL, + image_type VARCHAR(10) NOT NULL, + maps_version VARCHAR(50), + file_path VARCHAR(500) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + diff --git a/SatelliteProvider.DataAccess/Migrations/002_CreateRegionsTable.sql b/SatelliteProvider.DataAccess/Migrations/002_CreateRegionsTable.sql new file mode 100644 index 0000000..5b6556d --- /dev/null +++ b/SatelliteProvider.DataAccess/Migrations/002_CreateRegionsTable.sql @@ -0,0 +1,15 @@ +CREATE TABLE regions ( + id UUID PRIMARY KEY, + latitude DOUBLE PRECISION NOT NULL, + longitude DOUBLE PRECISION NOT NULL, + size_meters DOUBLE PRECISION NOT NULL, + zoom_level INT NOT NULL, + status VARCHAR(20) NOT NULL, + csv_file_path VARCHAR(500), + summary_file_path VARCHAR(500), + tiles_downloaded INT DEFAULT 0, + tiles_reused INT DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); + diff --git a/SatelliteProvider.DataAccess/Migrations/003_CreateIndexes.sql b/SatelliteProvider.DataAccess/Migrations/003_CreateIndexes.sql new file mode 100644 index 0000000..49a7187 --- /dev/null +++ b/SatelliteProvider.DataAccess/Migrations/003_CreateIndexes.sql @@ -0,0 +1,4 @@ +CREATE INDEX idx_tiles_composite ON tiles(latitude, longitude, tile_size_meters); +CREATE INDEX idx_tiles_zoom ON tiles(zoom_level); +CREATE INDEX idx_regions_status ON regions(status); + diff --git a/SatelliteProvider.DataAccess/Models/RegionEntity.cs b/SatelliteProvider.DataAccess/Models/RegionEntity.cs new file mode 100644 index 0000000..b1bb61b --- /dev/null +++ b/SatelliteProvider.DataAccess/Models/RegionEntity.cs @@ -0,0 +1,18 @@ +namespace SatelliteProvider.DataAccess.Models; + +public class RegionEntity +{ + public Guid Id { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } + public double SizeMeters { get; set; } + public int ZoomLevel { get; set; } + public string Status { get; set; } = string.Empty; + public string? CsvFilePath { get; set; } + public string? SummaryFilePath { get; set; } + public int TilesDownloaded { get; set; } + public int TilesReused { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} + diff --git a/SatelliteProvider.DataAccess/Models/TileEntity.cs b/SatelliteProvider.DataAccess/Models/TileEntity.cs new file mode 100644 index 0000000..5fcb2dd --- /dev/null +++ b/SatelliteProvider.DataAccess/Models/TileEntity.cs @@ -0,0 +1,17 @@ +namespace SatelliteProvider.DataAccess.Models; + +public class TileEntity +{ + public Guid Id { get; set; } + public int ZoomLevel { get; set; } + public double Latitude { get; set; } + public double Longitude { get; set; } + public double TileSizeMeters { get; set; } + public int TileSizePixels { get; set; } + public string ImageType { get; set; } = string.Empty; + public string? MapsVersion { get; set; } + public string FilePath { get; set; } = string.Empty; + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } +} + diff --git a/SatelliteProvider.DataAccess/Repositories/IRegionRepository.cs b/SatelliteProvider.DataAccess/Repositories/IRegionRepository.cs new file mode 100644 index 0000000..ee2d155 --- /dev/null +++ b/SatelliteProvider.DataAccess/Repositories/IRegionRepository.cs @@ -0,0 +1,13 @@ +using SatelliteProvider.DataAccess.Models; + +namespace SatelliteProvider.DataAccess.Repositories; + +public interface IRegionRepository +{ + Task GetByIdAsync(Guid id); + Task> GetByStatusAsync(string status); + Task InsertAsync(RegionEntity region); + Task UpdateAsync(RegionEntity region); + Task DeleteAsync(Guid id); +} + diff --git a/SatelliteProvider.DataAccess/Repositories/ITileRepository.cs b/SatelliteProvider.DataAccess/Repositories/ITileRepository.cs new file mode 100644 index 0000000..15f93c3 --- /dev/null +++ b/SatelliteProvider.DataAccess/Repositories/ITileRepository.cs @@ -0,0 +1,14 @@ +using SatelliteProvider.DataAccess.Models; + +namespace SatelliteProvider.DataAccess.Repositories; + +public interface ITileRepository +{ + Task GetByIdAsync(Guid id); + Task FindExistingTileAsync(double latitude, double longitude, double tileSizeMeters, int zoomLevel); + Task> GetTilesByRegionAsync(double latitude, double longitude, double sizeMeters, int zoomLevel); + Task InsertAsync(TileEntity tile); + Task UpdateAsync(TileEntity tile); + Task DeleteAsync(Guid id); +} + diff --git a/SatelliteProvider.DataAccess/Repositories/RegionRepository.cs b/SatelliteProvider.DataAccess/Repositories/RegionRepository.cs new file mode 100644 index 0000000..a4333ea --- /dev/null +++ b/SatelliteProvider.DataAccess/Repositories/RegionRepository.cs @@ -0,0 +1,89 @@ +using Dapper; +using Npgsql; +using SatelliteProvider.DataAccess.Models; + +namespace SatelliteProvider.DataAccess.Repositories; + +public class RegionRepository : IRegionRepository +{ + private readonly string _connectionString; + + public RegionRepository(string connectionString) + { + _connectionString = connectionString; + } + + public async Task GetByIdAsync(Guid id) + { + using var connection = new NpgsqlConnection(_connectionString); + const string sql = @" + SELECT id, latitude, longitude, size_meters as SizeMeters, + zoom_level as ZoomLevel, status, + csv_file_path as CsvFilePath, summary_file_path as SummaryFilePath, + tiles_downloaded as TilesDownloaded, tiles_reused as TilesReused, + created_at as CreatedAt, updated_at as UpdatedAt + FROM regions + WHERE id = @Id"; + + return await connection.QuerySingleOrDefaultAsync(sql, new { Id = id }); + } + + public async Task> GetByStatusAsync(string status) + { + using var connection = new NpgsqlConnection(_connectionString); + const string sql = @" + SELECT id, latitude, longitude, size_meters as SizeMeters, + zoom_level as ZoomLevel, status, + csv_file_path as CsvFilePath, summary_file_path as SummaryFilePath, + tiles_downloaded as TilesDownloaded, tiles_reused as TilesReused, + created_at as CreatedAt, updated_at as UpdatedAt + FROM regions + WHERE status = @Status + ORDER BY created_at ASC"; + + return await connection.QueryAsync(sql, new { Status = status }); + } + + public async Task InsertAsync(RegionEntity region) + { + using var connection = new NpgsqlConnection(_connectionString); + const string sql = @" + INSERT INTO regions (id, latitude, longitude, size_meters, zoom_level, + status, csv_file_path, summary_file_path, + tiles_downloaded, tiles_reused, created_at, updated_at) + VALUES (@Id, @Latitude, @Longitude, @SizeMeters, @ZoomLevel, + @Status, @CsvFilePath, @SummaryFilePath, + @TilesDownloaded, @TilesReused, @CreatedAt, @UpdatedAt) + RETURNING id"; + + return await connection.ExecuteScalarAsync(sql, region); + } + + public async Task UpdateAsync(RegionEntity region) + { + using var connection = new NpgsqlConnection(_connectionString); + const string sql = @" + UPDATE regions + SET latitude = @Latitude, + longitude = @Longitude, + size_meters = @SizeMeters, + zoom_level = @ZoomLevel, + status = @Status, + csv_file_path = @CsvFilePath, + summary_file_path = @SummaryFilePath, + tiles_downloaded = @TilesDownloaded, + tiles_reused = @TilesReused, + updated_at = @UpdatedAt + WHERE id = @Id"; + + return await connection.ExecuteAsync(sql, region); + } + + public async Task DeleteAsync(Guid id) + { + using var connection = new NpgsqlConnection(_connectionString); + const string sql = "DELETE FROM regions WHERE id = @Id"; + return await connection.ExecuteAsync(sql, new { Id = id }); + } +} + diff --git a/SatelliteProvider.DataAccess/Repositories/TileRepository.cs b/SatelliteProvider.DataAccess/Repositories/TileRepository.cs new file mode 100644 index 0000000..a5b4ce2 --- /dev/null +++ b/SatelliteProvider.DataAccess/Repositories/TileRepository.cs @@ -0,0 +1,123 @@ +using Dapper; +using Npgsql; +using SatelliteProvider.DataAccess.Models; + +namespace SatelliteProvider.DataAccess.Repositories; + +public class TileRepository : ITileRepository +{ + private readonly string _connectionString; + + public TileRepository(string connectionString) + { + _connectionString = connectionString; + } + + public async Task GetByIdAsync(Guid id) + { + using var connection = new NpgsqlConnection(_connectionString); + const string sql = @" + SELECT id, zoom_level as ZoomLevel, latitude, longitude, + tile_size_meters as TileSizeMeters, tile_size_pixels as TileSizePixels, + image_type as ImageType, maps_version as MapsVersion, + file_path as FilePath, created_at as CreatedAt, updated_at as UpdatedAt + FROM tiles + WHERE id = @Id"; + + return await connection.QuerySingleOrDefaultAsync(sql, new { Id = id }); + } + + public async Task FindExistingTileAsync(double latitude, double longitude, double tileSizeMeters, int zoomLevel) + { + using var connection = new NpgsqlConnection(_connectionString); + const string sql = @" + SELECT id, zoom_level as ZoomLevel, latitude, longitude, + tile_size_meters as TileSizeMeters, tile_size_pixels as TileSizePixels, + image_type as ImageType, maps_version as MapsVersion, + file_path as FilePath, created_at as CreatedAt, updated_at as UpdatedAt + FROM tiles + WHERE ABS(latitude - @Latitude) < 0.0001 + AND ABS(longitude - @Longitude) < 0.0001 + AND ABS(tile_size_meters - @TileSizeMeters) < 1 + AND zoom_level = @ZoomLevel + LIMIT 1"; + + return await connection.QuerySingleOrDefaultAsync(sql, new + { + Latitude = latitude, + Longitude = longitude, + TileSizeMeters = tileSizeMeters, + ZoomLevel = zoomLevel + }); + } + + public async Task> GetTilesByRegionAsync(double latitude, double longitude, double sizeMeters, int zoomLevel) + { + using var connection = new NpgsqlConnection(_connectionString); + + var latRange = sizeMeters / 111000.0; + var lonRange = sizeMeters / (111000.0 * Math.Cos(latitude * Math.PI / 180.0)); + + const string sql = @" + SELECT id, zoom_level as ZoomLevel, latitude, longitude, + tile_size_meters as TileSizeMeters, tile_size_pixels as TileSizePixels, + image_type as ImageType, maps_version as MapsVersion, + file_path as FilePath, created_at as CreatedAt, updated_at as UpdatedAt + FROM tiles + WHERE latitude BETWEEN @MinLat AND @MaxLat + AND longitude BETWEEN @MinLon AND @MaxLon + AND zoom_level = @ZoomLevel + ORDER BY latitude DESC, longitude ASC"; + + return await connection.QueryAsync(sql, new + { + MinLat = latitude - latRange / 2, + MaxLat = latitude + latRange / 2, + MinLon = longitude - lonRange / 2, + MaxLon = longitude + lonRange / 2, + ZoomLevel = zoomLevel + }); + } + + public async Task InsertAsync(TileEntity tile) + { + using var connection = new NpgsqlConnection(_connectionString); + const string sql = @" + INSERT INTO tiles (id, zoom_level, latitude, longitude, tile_size_meters, + tile_size_pixels, image_type, maps_version, file_path, + created_at, updated_at) + VALUES (@Id, @ZoomLevel, @Latitude, @Longitude, @TileSizeMeters, + @TileSizePixels, @ImageType, @MapsVersion, @FilePath, + @CreatedAt, @UpdatedAt) + RETURNING id"; + + return await connection.ExecuteScalarAsync(sql, tile); + } + + public async Task UpdateAsync(TileEntity tile) + { + using var connection = new NpgsqlConnection(_connectionString); + const string sql = @" + UPDATE tiles + SET zoom_level = @ZoomLevel, + latitude = @Latitude, + longitude = @Longitude, + tile_size_meters = @TileSizeMeters, + tile_size_pixels = @TileSizePixels, + image_type = @ImageType, + maps_version = @MapsVersion, + file_path = @FilePath, + updated_at = @UpdatedAt + WHERE id = @Id"; + + return await connection.ExecuteAsync(sql, tile); + } + + public async Task DeleteAsync(Guid id) + { + using var connection = new NpgsqlConnection(_connectionString); + const string sql = "DELETE FROM tiles WHERE id = @Id"; + return await connection.ExecuteAsync(sql, new { Id = id }); + } +} + diff --git a/SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj b/SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj new file mode 100644 index 0000000..86dc555 --- /dev/null +++ b/SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + diff --git a/SatelliteProvider.sln b/SatelliteProvider.sln index 7428785..e56bd89 100644 --- a/SatelliteProvider.sln +++ b/SatelliteProvider.sln @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.Services" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.Tests", "SatelliteProvider.Tests\SatelliteProvider.Tests.csproj", "{A44A2E49-9270-4938-9D34-A31CE63E636C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatelliteProvider.DataAccess", "SatelliteProvider.DataAccess\SatelliteProvider.DataAccess.csproj", "{8709915B-313D-4CFA-9E0D-0B312F3EA5C8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -31,5 +33,9 @@ Global {A44A2E49-9270-4938-9D34-A31CE63E636C}.Debug|Any CPU.Build.0 = Debug|Any CPU {A44A2E49-9270-4938-9D34-A31CE63E636C}.Release|Any CPU.ActiveCfg = Release|Any CPU {A44A2E49-9270-4938-9D34-A31CE63E636C}.Release|Any CPU.Build.0 = Release|Any CPU + {8709915B-313D-4CFA-9E0D-0B312F3EA5C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8709915B-313D-4CFA-9E0D-0B312F3EA5C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8709915B-313D-4CFA-9E0D-0B312F3EA5C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8709915B-313D-4CFA-9E0D-0B312F3EA5C8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..29f56da --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,21 @@ +services: + postgres: + image: postgres:16 + container_name: satellite-provider-postgres + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: satelliteprovider + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 + +volumes: + postgres_data: +