From b3756238a38ead9e4a4ef5f5cd750f393f4e2043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ty=C5=84ski?= Date: Sun, 4 Oct 2026 01:49:51 +0200 Subject: [PATCH] feat(backend): restructure API into layers with OpenAPI, versioning and error handling Split the backend into API host, Domain, Application and Infrastructure projects. Add Swagger/OpenAPI per API version, Asp.Versioning (query/header), ProblemDetails with a global exception handler and SurrealQL migrations applied at startup (namespace/database ensured). Infrastructure owns the SurrealDB client, settings and DI. --- .../Abstractions/IMigrationRunner.cs | 6 + .../Abstractions}/IStorageService.cs | 4 +- .../Dtos}/StorageModels.cs | 2 +- .../Application/TarninaHub.Application.csproj | 13 +++ .../Configuration/ConfigureSwaggerOptions.cs | 29 +++++ backend/Controllers/SystemController.cs | 6 +- backend/Domain/Enums/ApplicationStatus.cs | 9 ++ backend/Domain/Enums/DocumentScope.cs | 9 ++ backend/Domain/Enums/GrantCallStatus.cs | 8 ++ backend/Domain/Enums/IdeaStatus.cs | 10 ++ backend/Domain/Enums/InnovationStage.cs | 9 ++ backend/Domain/Enums/LocationKind.cs | 8 ++ backend/Domain/Enums/PartnershipStatus.cs | 8 ++ backend/Domain/Enums/SubmissionStatus.cs | 11 ++ backend/Domain/Enums/TagGroup.cs | 10 ++ backend/Domain/Enums/UserRole.cs | 10 ++ backend/Domain/TarninaHub.Domain.csproj | 9 ++ backend/Errors/GlobalExceptionHandler.cs | 38 +++++++ backend/Infrastructure/DependencyInjection.cs | 52 +++++++++ .../Persistence/MigrationRunner.cs | 63 +++++++++++ .../Persistence}/StorageService.cs | 7 +- .../Settings/SurrealDbEndpoint.cs | 21 ++++ .../Settings/SurrealDbSettings.cs | 8 +- .../TarninaHub.Infrastructure.csproj | 20 ++++ backend/Program.cs | 103 +++++++++--------- backend/TarninaHub.csproj | 27 ++++- 26 files changed, 437 insertions(+), 63 deletions(-) create mode 100644 backend/Application/Abstractions/IMigrationRunner.cs rename backend/{Services => Application/Abstractions}/IStorageService.cs (77%) rename backend/{Models => Application/Dtos}/StorageModels.cs (92%) create mode 100644 backend/Application/TarninaHub.Application.csproj create mode 100644 backend/Configuration/ConfigureSwaggerOptions.cs create mode 100644 backend/Domain/Enums/ApplicationStatus.cs create mode 100644 backend/Domain/Enums/DocumentScope.cs create mode 100644 backend/Domain/Enums/GrantCallStatus.cs create mode 100644 backend/Domain/Enums/IdeaStatus.cs create mode 100644 backend/Domain/Enums/InnovationStage.cs create mode 100644 backend/Domain/Enums/LocationKind.cs create mode 100644 backend/Domain/Enums/PartnershipStatus.cs create mode 100644 backend/Domain/Enums/SubmissionStatus.cs create mode 100644 backend/Domain/Enums/TagGroup.cs create mode 100644 backend/Domain/Enums/UserRole.cs create mode 100644 backend/Domain/TarninaHub.Domain.csproj create mode 100644 backend/Errors/GlobalExceptionHandler.cs create mode 100644 backend/Infrastructure/DependencyInjection.cs create mode 100644 backend/Infrastructure/Persistence/MigrationRunner.cs rename backend/{Services => Infrastructure/Persistence}/StorageService.cs (90%) create mode 100644 backend/Infrastructure/Settings/SurrealDbEndpoint.cs rename backend/{ => Infrastructure}/Settings/SurrealDbSettings.cs (65%) create mode 100644 backend/Infrastructure/TarninaHub.Infrastructure.csproj diff --git a/backend/Application/Abstractions/IMigrationRunner.cs b/backend/Application/Abstractions/IMigrationRunner.cs new file mode 100644 index 0000000..513c627 --- /dev/null +++ b/backend/Application/Abstractions/IMigrationRunner.cs @@ -0,0 +1,6 @@ +namespace TarninaHub.Application.Abstractions; + +public interface IMigrationRunner +{ + Task ApplyAsync(CancellationToken cancellationToken); +} diff --git a/backend/Services/IStorageService.cs b/backend/Application/Abstractions/IStorageService.cs similarity index 77% rename from backend/Services/IStorageService.cs rename to backend/Application/Abstractions/IStorageService.cs index cf170f7..c5a0add 100644 --- a/backend/Services/IStorageService.cs +++ b/backend/Application/Abstractions/IStorageService.cs @@ -1,6 +1,6 @@ -using TarninaHub.Models; +using TarninaHub.Application.Dtos; -namespace TarninaHub.Services; +namespace TarninaHub.Application.Abstractions; public interface IStorageService { diff --git a/backend/Models/StorageModels.cs b/backend/Application/Dtos/StorageModels.cs similarity index 92% rename from backend/Models/StorageModels.cs rename to backend/Application/Dtos/StorageModels.cs index 00ceb4b..9e1e635 100644 --- a/backend/Models/StorageModels.cs +++ b/backend/Application/Dtos/StorageModels.cs @@ -1,4 +1,4 @@ -namespace TarninaHub.Models; +namespace TarninaHub.Application.Dtos; public sealed class StorageProbe { diff --git a/backend/Application/TarninaHub.Application.csproj b/backend/Application/TarninaHub.Application.csproj new file mode 100644 index 0000000..60453b2 --- /dev/null +++ b/backend/Application/TarninaHub.Application.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/backend/Configuration/ConfigureSwaggerOptions.cs b/backend/Configuration/ConfigureSwaggerOptions.cs new file mode 100644 index 0000000..76aa4e9 --- /dev/null +++ b/backend/Configuration/ConfigureSwaggerOptions.cs @@ -0,0 +1,29 @@ +using Asp.Versioning.ApiExplorer; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace TarninaHub.Configuration; + +public sealed class ConfigureSwaggerOptions : IConfigureOptions +{ + private readonly IApiVersionDescriptionProvider _provider; + + public ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider) + { + _provider = provider; + } + + public void Configure(SwaggerGenOptions options) + { + foreach (var description in _provider.ApiVersionDescriptions) + { + options.SwaggerDoc(description.GroupName, new OpenApiInfo + { + Title = "TarninaHub API", + Version = description.ApiVersion.ToString(), + Description = "Backend API (BFF) for TarninaHub — Małopolski Hub Innowacji Społecznych.", + }); + } + } +} diff --git a/backend/Controllers/SystemController.cs b/backend/Controllers/SystemController.cs index 9d052a4..e33e53b 100644 --- a/backend/Controllers/SystemController.cs +++ b/backend/Controllers/SystemController.cs @@ -1,10 +1,12 @@ +using Asp.Versioning; using Microsoft.AspNetCore.Mvc; -using TarninaHub.Models; -using TarninaHub.Services; +using TarninaHub.Application.Abstractions; +using TarninaHub.Application.Dtos; namespace TarninaHub.Controllers; [ApiController] +[ApiVersion("1.0")] [Route("api/system")] public class SystemController : ControllerBase { diff --git a/backend/Domain/Enums/ApplicationStatus.cs b/backend/Domain/Enums/ApplicationStatus.cs new file mode 100644 index 0000000..36a1f83 --- /dev/null +++ b/backend/Domain/Enums/ApplicationStatus.cs @@ -0,0 +1,9 @@ +namespace TarninaHub.Domain.Enums; + +public enum ApplicationStatus +{ + Draft, + Submitted, + Accepted, + Rejected, +} diff --git a/backend/Domain/Enums/DocumentScope.cs b/backend/Domain/Enums/DocumentScope.cs new file mode 100644 index 0000000..8499103 --- /dev/null +++ b/backend/Domain/Enums/DocumentScope.cs @@ -0,0 +1,9 @@ +namespace TarninaHub.Domain.Enums; + +public enum DocumentScope +{ + Local, + Partial, + Full, + Anonymized, +} diff --git a/backend/Domain/Enums/GrantCallStatus.cs b/backend/Domain/Enums/GrantCallStatus.cs new file mode 100644 index 0000000..b25dda1 --- /dev/null +++ b/backend/Domain/Enums/GrantCallStatus.cs @@ -0,0 +1,8 @@ +namespace TarninaHub.Domain.Enums; + +public enum GrantCallStatus +{ + Planned, + Open, + Closed, +} diff --git a/backend/Domain/Enums/IdeaStatus.cs b/backend/Domain/Enums/IdeaStatus.cs new file mode 100644 index 0000000..fba8b0c --- /dev/null +++ b/backend/Domain/Enums/IdeaStatus.cs @@ -0,0 +1,10 @@ +namespace TarninaHub.Domain.Enums; + +public enum IdeaStatus +{ + Draft, + Submitted, + InReview, + Accepted, + Rejected, +} diff --git a/backend/Domain/Enums/InnovationStage.cs b/backend/Domain/Enums/InnovationStage.cs new file mode 100644 index 0000000..a7b2440 --- /dev/null +++ b/backend/Domain/Enums/InnovationStage.cs @@ -0,0 +1,9 @@ +namespace TarninaHub.Domain.Enums; + +public enum InnovationStage +{ + Idea, + Pilot, + Scaling, + Service, +} diff --git a/backend/Domain/Enums/LocationKind.cs b/backend/Domain/Enums/LocationKind.cs new file mode 100644 index 0000000..02ef325 --- /dev/null +++ b/backend/Domain/Enums/LocationKind.cs @@ -0,0 +1,8 @@ +namespace TarninaHub.Domain.Enums; + +public enum LocationKind +{ + Gmina, + Powiat, + Wojewodztwo, +} diff --git a/backend/Domain/Enums/PartnershipStatus.cs b/backend/Domain/Enums/PartnershipStatus.cs new file mode 100644 index 0000000..acbe16e --- /dev/null +++ b/backend/Domain/Enums/PartnershipStatus.cs @@ -0,0 +1,8 @@ +namespace TarninaHub.Domain.Enums; + +public enum PartnershipStatus +{ + Proposed, + Active, + Closed, +} diff --git a/backend/Domain/Enums/SubmissionStatus.cs b/backend/Domain/Enums/SubmissionStatus.cs new file mode 100644 index 0000000..1845f5c --- /dev/null +++ b/backend/Domain/Enums/SubmissionStatus.cs @@ -0,0 +1,11 @@ +namespace TarninaHub.Domain.Enums; + +public enum SubmissionStatus +{ + New, + Triaged, + Matched, + InProgress, + Closed, + Rejected, +} diff --git a/backend/Domain/Enums/TagGroup.cs b/backend/Domain/Enums/TagGroup.cs new file mode 100644 index 0000000..90f379e --- /dev/null +++ b/backend/Domain/Enums/TagGroup.cs @@ -0,0 +1,10 @@ +namespace TarninaHub.Domain.Enums; + +public enum TagGroup +{ + Audience, + Topic, + Stage, + Sector, + Cross, +} diff --git a/backend/Domain/Enums/UserRole.cs b/backend/Domain/Enums/UserRole.cs new file mode 100644 index 0000000..f96516c --- /dev/null +++ b/backend/Domain/Enums/UserRole.cs @@ -0,0 +1,10 @@ +namespace TarninaHub.Domain.Enums; + +public enum UserRole +{ + User, + Jst, + Operator, + Manager, + Admin, +} diff --git a/backend/Domain/TarninaHub.Domain.csproj b/backend/Domain/TarninaHub.Domain.csproj new file mode 100644 index 0000000..9ed914b --- /dev/null +++ b/backend/Domain/TarninaHub.Domain.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/backend/Errors/GlobalExceptionHandler.cs b/backend/Errors/GlobalExceptionHandler.cs new file mode 100644 index 0000000..40cc0b7 --- /dev/null +++ b/backend/Errors/GlobalExceptionHandler.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Mvc; + +namespace TarninaHub.Errors; + +public sealed class GlobalExceptionHandler : IExceptionHandler +{ + private readonly ILogger _logger; + + public GlobalExceptionHandler(ILogger logger) + { + _logger = logger; + } + + public async ValueTask TryHandleAsync( + HttpContext httpContext, + Exception exception, + CancellationToken cancellationToken) + { + _logger.LogError(exception, "Unhandled exception for {Method} {Path}", + httpContext.Request.Method, + httpContext.Request.Path); + + var problem = new ProblemDetails + { + Type = "https://httpstatuses.io/500", + Title = "An unexpected error occurred.", + Status = StatusCodes.Status500InternalServerError, + Instance = httpContext.Request.Path, + }; + + httpContext.Response.StatusCode = problem.Status.Value; + + await httpContext.Response.WriteAsJsonAsync(problem, cancellationToken); + + return true; + } +} diff --git a/backend/Infrastructure/DependencyInjection.cs b/backend/Infrastructure/DependencyInjection.cs new file mode 100644 index 0000000..51897e6 --- /dev/null +++ b/backend/Infrastructure/DependencyInjection.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using SurrealDb.Net; +using TarninaHub.Application.Abstractions; +using TarninaHub.Infrastructure.Persistence; +using TarninaHub.Infrastructure.Settings; + +namespace TarninaHub.Infrastructure; + +public static class DependencyInjection +{ + public static IServiceCollection AddInfrastructure( + this IServiceCollection services, + IConfiguration configuration) + { + services.Configure(configuration.GetSection("SurrealDb")); + + var settings = configuration.GetSection("SurrealDb").Get() + ?? new SurrealDbSettings(); + + var options = new SurrealDbOptions + { + Endpoint = SurrealDbEndpoint.Build(settings), + Namespace = settings.Namespace, + Database = settings.Database, + }; + + if (string.IsNullOrWhiteSpace(settings.RemoteUrl)) + { + // Embedded RocksDB, persisted next to the running app (./data), never in memory. + var dataDirectory = Path.Combine(AppContext.BaseDirectory, settings.DataDirectory); + Directory.CreateDirectory(dataDirectory); + + services + .AddSurreal(options, ServiceLifetime.Singleton) + .AddRocksDbProvider(); + } + else + { + // Remote SurrealDB server on its own dedicated port (used for sync with the frontend). + options.Username = settings.Username; + options.Password = settings.Password; + + services.AddSurreal(options, ServiceLifetime.Singleton); + } + + services.AddSingleton(); + services.AddSingleton(); + + return services; + } +} diff --git a/backend/Infrastructure/Persistence/MigrationRunner.cs b/backend/Infrastructure/Persistence/MigrationRunner.cs new file mode 100644 index 0000000..47b7062 --- /dev/null +++ b/backend/Infrastructure/Persistence/MigrationRunner.cs @@ -0,0 +1,63 @@ +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using SurrealDb.Net; +using TarninaHub.Application.Abstractions; +using TarninaHub.Infrastructure.Settings; + +namespace TarninaHub.Infrastructure.Persistence; + +public sealed class MigrationRunner : IMigrationRunner +{ + private readonly ISurrealDbClient _client; + private readonly SurrealDbSettings _settings; + private readonly ILogger _logger; + + public MigrationRunner( + ISurrealDbClient client, + IOptions settings, + ILogger logger) + { + _client = client; + _settings = settings.Value; + _logger = logger; + } + + public async Task ApplyAsync(CancellationToken cancellationToken) + { + await EnsureNamespaceAsync(cancellationToken); + + var migrationsPath = Path.Combine(AppContext.BaseDirectory, _settings.MigrationsDirectory); + + if (!Directory.Exists(migrationsPath)) + { + _logger.LogWarning( + "Migrations directory '{MigrationsPath}' was not found; skipping migrations.", + migrationsPath); + + return; + } + + var files = Directory.GetFiles(migrationsPath, "*.surql") + .OrderBy(file => file, StringComparer.Ordinal); + + foreach (var file in files) + { + var sql = await File.ReadAllTextAsync(file, cancellationToken); + + await _client.RawQuery(sql, cancellationToken: cancellationToken); + + _logger.LogInformation("Applied migration {Migration}.", Path.GetFileName(file)); + } + } + + private async Task EnsureNamespaceAsync(CancellationToken cancellationToken) + { + var sql = + $"DEFINE NAMESPACE IF NOT EXISTS {_settings.Namespace}; " + + $"USE NS {_settings.Namespace}; " + + $"DEFINE DATABASE IF NOT EXISTS {_settings.Database}; " + + $"USE DB {_settings.Database};"; + + await _client.RawQuery(sql, cancellationToken: cancellationToken); + } +} diff --git a/backend/Services/StorageService.cs b/backend/Infrastructure/Persistence/StorageService.cs similarity index 90% rename from backend/Services/StorageService.cs rename to backend/Infrastructure/Persistence/StorageService.cs index 40c6cfc..bf74816 100644 --- a/backend/Services/StorageService.cs +++ b/backend/Infrastructure/Persistence/StorageService.cs @@ -1,9 +1,10 @@ using Microsoft.Extensions.Options; using SurrealDb.Net; -using TarninaHub.Models; -using TarninaHub.Settings; +using TarninaHub.Application.Abstractions; +using TarninaHub.Application.Dtos; +using TarninaHub.Infrastructure.Settings; -namespace TarninaHub.Services; +namespace TarninaHub.Infrastructure.Persistence; public sealed class StorageService : IStorageService { diff --git a/backend/Infrastructure/Settings/SurrealDbEndpoint.cs b/backend/Infrastructure/Settings/SurrealDbEndpoint.cs new file mode 100644 index 0000000..40db458 --- /dev/null +++ b/backend/Infrastructure/Settings/SurrealDbEndpoint.cs @@ -0,0 +1,21 @@ +namespace TarninaHub.Infrastructure.Settings; + +public static class SurrealDbEndpoint +{ + /// + /// Builds the SurrealDB endpoint. Remote mode uses the configured URL; + /// embedded mode uses the RocksDB provider persisted to the data directory + /// (forward slashes, so the same endpoint works on Windows and Linux). + /// + public static string Build(SurrealDbSettings settings) + { + if (!string.IsNullOrWhiteSpace(settings.RemoteUrl)) + { + return settings.RemoteUrl; + } + + var dataDirectory = Path.Combine(AppContext.BaseDirectory, settings.DataDirectory); + + return $"rocksdb://{dataDirectory.Replace('\\', '/')}"; + } +} diff --git a/backend/Settings/SurrealDbSettings.cs b/backend/Infrastructure/Settings/SurrealDbSettings.cs similarity index 65% rename from backend/Settings/SurrealDbSettings.cs rename to backend/Infrastructure/Settings/SurrealDbSettings.cs index 2295891..bdbc1b1 100644 --- a/backend/Settings/SurrealDbSettings.cs +++ b/backend/Infrastructure/Settings/SurrealDbSettings.cs @@ -1,4 +1,4 @@ -namespace TarninaHub.Settings; +namespace TarninaHub.Infrastructure.Settings; public sealed class SurrealDbSettings { @@ -8,6 +8,12 @@ public sealed class SurrealDbSettings public string DataDirectory { get; set; } = "data"; + /// + /// Directory (relative to the app base directory) with ordered *.surql + /// migration files applied at startup. Copied from backend/migrations. + /// + public string MigrationsDirectory { get; set; } = "migrations"; + /// /// When set (e.g. ws://127.0.0.1:8010/rpc), the backend connects to a /// dedicated SurrealDB server instead of the embedded RocksDB engine. diff --git a/backend/Infrastructure/TarninaHub.Infrastructure.csproj b/backend/Infrastructure/TarninaHub.Infrastructure.csproj new file mode 100644 index 0000000..ee76d1b --- /dev/null +++ b/backend/Infrastructure/TarninaHub.Infrastructure.csproj @@ -0,0 +1,20 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + + diff --git a/backend/Program.cs b/backend/Program.cs index 1206fc1..3793e3b 100644 --- a/backend/Program.cs +++ b/backend/Program.cs @@ -1,61 +1,74 @@ -using TarninaHub.Services; -using TarninaHub.Settings; +using Asp.Versioning; +using Asp.Versioning.ApiExplorer; +using Microsoft.Extensions.Options; +using Swashbuckle.AspNetCore.SwaggerGen; +using TarninaHub.Application.Abstractions; +using TarninaHub.Configuration; +using TarninaHub.Errors; +using TarninaHub.Infrastructure; var builder = WebApplication.CreateBuilder(args); -var surrealSettings = builder.Configuration.GetSection("SurrealDb").Get() - ?? new SurrealDbSettings(); - -builder.Services.Configure(builder.Configuration.GetSection("SurrealDb")); - -// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi -builder.Services.AddOpenApi(); +// Layered infrastructure: SurrealDB client, settings and persistence services. +builder.Services.AddInfrastructure(builder.Configuration); builder.Services.AddControllers(); -builder.Services.AddSingleton(); +// Uniform error responses (RFC 7807) and a global exception handler. +builder.Services.AddProblemDetails(); +builder.Services.AddExceptionHandler(); -var surrealOptions = new SurrealDbOptions -{ - Endpoint = BuildEndpoint(surrealSettings), - Namespace = surrealSettings.Namespace, - Database = surrealSettings.Database, -}; +// API versioning: `?api-version=1.0` or header `X-Api-Version`; default 1.0. +builder.Services + .AddApiVersioning(options => + { + options.DefaultApiVersion = new ApiVersion(1, 0); + options.AssumeDefaultVersionWhenUnspecified = true; + options.ApiVersionReader = ApiVersionReader.Combine( + new QueryStringApiVersionReader("api-version"), + new HeaderApiVersionReader("X-Api-Version")); + options.ReportApiVersions = true; + }) + .AddMvc() + .AddApiExplorer(options => + { + options.GroupNameFormat = "'v'VVV"; + options.SubstituteApiVersionInUrl = true; + }); -if (string.IsNullOrWhiteSpace(surrealSettings.RemoteUrl)) -{ - // Embedded RocksDB, persisted next to the running app (./data), never in memory. - var dataDirectory = Path.Combine(AppContext.BaseDirectory, surrealSettings.DataDirectory); - Directory.CreateDirectory(dataDirectory); - - builder.Services - .AddSurreal(surrealOptions, ServiceLifetime.Singleton) - .AddRocksDbProvider(); -} -else -{ - // Remote SurrealDB server on its own dedicated port (used for sync with the frontend). - surrealOptions.Username = surrealSettings.Username; - surrealOptions.Password = surrealSettings.Password; - - builder.Services.AddSurreal(surrealOptions, ServiceLifetime.Singleton); -} +// OpenAPI document + Swagger UI (Swashbuckle), one document per API version. +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddTransient, ConfigureSwaggerOptions>(); +builder.Services.AddSwaggerGen(); var app = builder.Build(); using (var scope = app.Services.CreateScope()) { + var migrationRunner = scope.ServiceProvider.GetRequiredService(); var storageService = scope.ServiceProvider.GetRequiredService(); + await migrationRunner.ApplyAsync(CancellationToken.None); await storageService.InitializeAsync(CancellationToken.None); } -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) +app.UseExceptionHandler(); +app.UseStatusCodePages(); + +app.UseSwagger(); +app.UseSwaggerUI(options => { - app.MapOpenApi(); -} -else + var provider = app.Services.GetRequiredService(); + + foreach (var description in provider.ApiVersionDescriptions) + { + options.SwaggerEndpoint( + $"/swagger/{description.GroupName}/swagger.json", + $"TarninaHub API {description.GroupName.ToUpperInvariant()}"); + } +}); + +if (!app.Environment.IsDevelopment()) { app.UseHttpsRedirection(); } @@ -68,15 +81,3 @@ app.MapControllers(); app.MapFallbackToFile("index.html"); app.Run(); - -static string BuildEndpoint(SurrealDbSettings settings) -{ - if (!string.IsNullOrWhiteSpace(settings.RemoteUrl)) - { - return settings.RemoteUrl; - } - - var dataDirectory = Path.Combine(AppContext.BaseDirectory, settings.DataDirectory); - - return $"rocksdb://{dataDirectory.Replace('\\', '/')}"; -} diff --git a/backend/TarninaHub.csproj b/backend/TarninaHub.csproj index 77db9f4..1081262 100644 --- a/backend/TarninaHub.csproj +++ b/backend/TarninaHub.csproj @@ -9,9 +9,30 @@ - - - + + + + + + + + + + + + + + + + + + + + + + +