Add server configuration service and repo
This commit is contained in:
@@ -1,13 +1,17 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using RecNet.Application.Services.Configuration;
|
||||||
|
|
||||||
namespace API.Controllers.Config.V1;
|
namespace API.Controllers.Config.V1;
|
||||||
|
|
||||||
[Route("api/[controller]/v1")]
|
[Route("api/[controller]/v1")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class ConfigController : ControllerBase
|
public class ConfigController(IConfigService configService) : ControllerBase
|
||||||
{
|
{
|
||||||
// TODO: Resolve from Database
|
|
||||||
[HttpGet("motd")]
|
[HttpGet("motd")]
|
||||||
public ActionResult<string> GetMotd()
|
public async Task<ActionResult<string>> GetMotd(CancellationToken ct = default)
|
||||||
=> Ok("Hello World!");
|
{
|
||||||
|
var motd = await configService.GetAsync("Config:MOTD", "Ten Whole Years!", ct);
|
||||||
|
|
||||||
|
return Ok(motd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using RecNet.Application.Services.Configuration;
|
||||||
|
|
||||||
namespace RecNet.Application;
|
namespace RecNet.Application;
|
||||||
|
|
||||||
@@ -7,6 +8,8 @@ public static class DependencyInjection
|
|||||||
public static IServiceCollection AddApplication(this IServiceCollection services)
|
public static IServiceCollection AddApplication(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddAutoMapper(_ => { }, typeof(DependencyInjection).Assembly);
|
services.AddAutoMapper(_ => { }, typeof(DependencyInjection).Assembly);
|
||||||
|
|
||||||
|
services.AddScoped<IConfigService, ConfigService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|||||||
33
RecNet.Application/Services/Configuration/ConfigService.cs
Normal file
33
RecNet.Application/Services/Configuration/ConfigService.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using RecNet.Domain.Repositories;
|
||||||
|
|
||||||
|
namespace RecNet.Application.Services.Configuration;
|
||||||
|
|
||||||
|
public class ConfigService(IServerConfigRepository repository) : IConfigService
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
|
||||||
|
|
||||||
|
public async Task<T?> GetAsync<T>(
|
||||||
|
string key,
|
||||||
|
T? defaultValue = default,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var config = await repository.GetAsync(key, ct);
|
||||||
|
if (config == null)
|
||||||
|
return defaultValue;
|
||||||
|
|
||||||
|
return JsonSerializer.Deserialize<T>(config.Value, JsonOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SetAsync<T>(
|
||||||
|
string key,
|
||||||
|
T value,
|
||||||
|
CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var json = JsonSerializer.Serialize(value, JsonOptions);
|
||||||
|
|
||||||
|
await repository.SetAsync(key, json, ct);
|
||||||
|
|
||||||
|
await repository.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace RecNet.Application.Services.Configuration;
|
||||||
|
|
||||||
|
public interface IConfigService
|
||||||
|
{
|
||||||
|
Task<T?> GetAsync<T>(string key, T? defaultValue = default, CancellationToken ct = default);
|
||||||
|
Task SetAsync<T>(string key, T value, CancellationToken ct = default);
|
||||||
|
}
|
||||||
24
RecNet.Domain/Entities/Configuration/ServerConfig.cs
Normal file
24
RecNet.Domain/Entities/Configuration/ServerConfig.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using RecNet.Domain.Exceptions;
|
||||||
|
|
||||||
|
namespace RecNet.Domain.Entities.Configuration;
|
||||||
|
|
||||||
|
public class ServerConfig
|
||||||
|
{
|
||||||
|
private ServerConfig(string key, string value)
|
||||||
|
{
|
||||||
|
Key = RequireValue(key, nameof(key));
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Key { get; private set; }
|
||||||
|
public string Value { get; private set; }
|
||||||
|
|
||||||
|
public static ServerConfig Create(string key, string value)
|
||||||
|
=> new(key, value);
|
||||||
|
|
||||||
|
public void SetValue(string value)
|
||||||
|
=> Value = value;
|
||||||
|
|
||||||
|
private static string RequireValue(string value, string parameterName)
|
||||||
|
=> !string.IsNullOrWhiteSpace(value) ? value : throw new DomainException($"{parameterName} is required.");
|
||||||
|
}
|
||||||
@@ -6,8 +6,4 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Entities\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
10
RecNet.Domain/Repositories/IServerConfigRepository.cs
Normal file
10
RecNet.Domain/Repositories/IServerConfigRepository.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using RecNet.Domain.Entities.Configuration;
|
||||||
|
|
||||||
|
namespace RecNet.Domain.Repositories;
|
||||||
|
|
||||||
|
public interface IServerConfigRepository
|
||||||
|
{
|
||||||
|
Task<ServerConfig?> GetAsync(string key, CancellationToken ct = default);
|
||||||
|
Task SetAsync(string key, string value, CancellationToken ct = default);
|
||||||
|
Task SaveChangesAsync(CancellationToken ct = default);
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ public static class DependencyInjection
|
|||||||
builder.AddNpgsqlDbContext<DatabaseContext>("recnet");
|
builder.AddNpgsqlDbContext<DatabaseContext>("recnet");
|
||||||
|
|
||||||
builder.Services.AddScoped<IProfileRepository, ProfileRepository>();
|
builder.Services.AddScoped<IProfileRepository, ProfileRepository>();
|
||||||
|
builder.Services.AddScoped<IServerConfigRepository, ServerConfigRepository>();
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
using RecNet.Domain.Entities.Configuration;
|
||||||
|
|
||||||
|
namespace RecNet.Infrastructure.Persistence.Configurations;
|
||||||
|
|
||||||
|
public class ServerConfigConfiguration : IEntityTypeConfiguration<ServerConfig>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<ServerConfig> builder)
|
||||||
|
{
|
||||||
|
builder.HasKey(x => x.Key);
|
||||||
|
|
||||||
|
builder.Property(x => x.Key)
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(x => x.Value)
|
||||||
|
.HasColumnType("jsonb")
|
||||||
|
.IsRequired();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using RecNet.Domain.Entities.Configuration;
|
||||||
using RecNet.Domain.Entities.Profiles;
|
using RecNet.Domain.Entities.Profiles;
|
||||||
|
|
||||||
namespace RecNet.Infrastructure.Persistence;
|
namespace RecNet.Infrastructure.Persistence;
|
||||||
@@ -6,6 +7,7 @@ namespace RecNet.Infrastructure.Persistence;
|
|||||||
public class DatabaseContext(DbContextOptions<DatabaseContext> options) : DbContext(options)
|
public class DatabaseContext(DbContextOptions<DatabaseContext> options) : DbContext(options)
|
||||||
{
|
{
|
||||||
public DbSet<Profile> Profiles => Set<Profile>();
|
public DbSet<Profile> Profiles => Set<Profile>();
|
||||||
|
public DbSet<ServerConfig> ServerConfigs => Set<ServerConfig>();
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
|||||||
81
RecNet.Infrastructure/Persistence/Migrations/20260618183916_ServerConfigs.Designer.cs
generated
Normal file
81
RecNet.Infrastructure/Persistence/Migrations/20260618183916_ServerConfigs.Designer.cs
generated
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using RecNet.Infrastructure.Persistence;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace RecNet.Infrastructure.Persistence.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(DatabaseContext))]
|
||||||
|
[Migration("20260618183916_ServerConfigs")]
|
||||||
|
partial class ServerConfigs
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "10.0.8")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("RecNet.Domain.Entities.Configuration.ServerConfig", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.HasKey("Key");
|
||||||
|
|
||||||
|
b.ToTable("ServerConfigs");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("RecNet.Domain.Entities.Profiles.Profile", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("ProfileId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("uuid");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset>("CreatedAt")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.PrimitiveCollection<string>("DeviceIds")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("character varying(32)");
|
||||||
|
|
||||||
|
b.Property<string>("Platform")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)");
|
||||||
|
|
||||||
|
b.Property<string>("PlatformId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(50)
|
||||||
|
.HasColumnType("character varying(50)");
|
||||||
|
|
||||||
|
b.HasKey("ProfileId");
|
||||||
|
|
||||||
|
b.HasIndex("Platform", "PlatformId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Profiles");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace RecNet.Infrastructure.Persistence.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class ServerConfigs : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ServerConfigs",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Key = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||||
|
Value = table.Column<string>(type: "jsonb", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ServerConfigs", x => x.Key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ServerConfigs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,21 @@ namespace RecNet.Infrastructure.Persistence.Migrations
|
|||||||
|
|
||||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("RecNet.Domain.Entities.Configuration.ServerConfig", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasMaxLength(128)
|
||||||
|
.HasColumnType("character varying(128)");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
b.HasKey("Key");
|
||||||
|
|
||||||
|
b.ToTable("ServerConfigs");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("RecNet.Domain.Entities.Profiles.Profile", b =>
|
modelBuilder.Entity("RecNet.Domain.Entities.Profiles.Profile", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("ProfileId")
|
b.Property<Guid>("ProfileId")
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ public class ProfileRepository(DatabaseContext dbContext) : IProfileRepository
|
|||||||
.Where(x => profileIds.Contains(x.ProfileId))
|
.Where(x => profileIds.Contains(x.ProfileId))
|
||||||
.ToListAsync(ct);
|
.ToListAsync(ct);
|
||||||
|
|
||||||
public Task<Profile?> GetByPlatform(PlatformType platform, string platformId, CancellationToken ct = default)
|
public Task<Profile?> GetByPlatform(
|
||||||
|
PlatformType platform,
|
||||||
|
string platformId,
|
||||||
|
CancellationToken ct = default)
|
||||||
=> dbContext.Profiles
|
=> dbContext.Profiles
|
||||||
.FirstOrDefaultAsync(x =>
|
.FirstOrDefaultAsync(x =>
|
||||||
x.Platform == platform &&
|
x.Platform == platform &&
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
|
using RecNet.Domain.Entities.Configuration;
|
||||||
|
using RecNet.Domain.Repositories;
|
||||||
|
|
||||||
|
namespace RecNet.Infrastructure.Persistence.Repositories;
|
||||||
|
|
||||||
|
public class ServerConfigRepository(DatabaseContext dbContext) : IServerConfigRepository
|
||||||
|
{
|
||||||
|
public Task<ServerConfig?> GetAsync(string key, CancellationToken ct = default)
|
||||||
|
=> dbContext.ServerConfigs
|
||||||
|
.FirstOrDefaultAsync(x => x.Key == key, ct);
|
||||||
|
|
||||||
|
public async Task SetAsync(string key, string value, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
var config = await GetAsync(key, ct);
|
||||||
|
|
||||||
|
if (config is null)
|
||||||
|
{
|
||||||
|
config = ServerConfig.Create(key, value);
|
||||||
|
await dbContext.ServerConfigs.AddAsync(config, ct);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.SetValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SaveChangesAsync(CancellationToken ct = default)
|
||||||
|
=> dbContext.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user