Skip to content

Commit 1ac5015

Browse files
authored
Add configuration service (#209)
1 parent 01d3ef0 commit 1ac5015

13 files changed

Lines changed: 2133 additions & 1 deletion
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using Microsoft.EntityFrameworkCore;
3+
using OpenShock.API.Controller.Admin.DTOs;
4+
using OpenShock.Common.Services.Configuration;
5+
6+
namespace OpenShock.API.Controller.Admin;
7+
8+
public sealed partial class AdminController
9+
{
10+
/// <summary>
11+
/// Gets all configuration items
12+
/// </summary>
13+
/// <response code="200"></response>
14+
/// <response code="401">Unauthorized</response>
15+
[HttpGet("config")]
16+
public IAsyncEnumerable<ConfigurationItemDto> ConfigurationList([FromServices] IConfigurationService configurationService)
17+
{
18+
return configurationService
19+
.GetAllItemsQuery()
20+
.Select(ci => new ConfigurationItemDto
21+
{
22+
Name = ci.Name,
23+
Description = ci.Description,
24+
Type = ci.Type,
25+
Value = ci.Value,
26+
UpdatedAt = ci.UpdatedAt,
27+
CreatedAt = ci.CreatedAt,
28+
})
29+
.AsAsyncEnumerable();
30+
}
31+
32+
/// <summary>
33+
/// Adds a new configuration
34+
/// </summary>
35+
/// <response code="200">Configuration added</response>
36+
/// <response code="400">Invalid name or value format</response>
37+
/// <response code="409">Already exists</response>
38+
/// <response code="401">Unauthorized</response>
39+
[HttpPost("config")]
40+
public async Task<IActionResult> ConfigurationAdd(
41+
[FromBody] ConfigurationAddItemRequest body,
42+
[FromServices] IConfigurationService configurationService,
43+
CancellationToken cancellationToken)
44+
{
45+
var result = await configurationService.TryAddItemAsync(
46+
body.Name,
47+
body.Description,
48+
body.Type,
49+
body.Value
50+
);
51+
52+
return result.Match<IActionResult>(
53+
success => Ok(),
54+
alreadyExists => Conflict(new { Error = "AlreadyExists", Message = $"A configuration named '{body.Name}' already exists." }),
55+
invalidName => BadRequest(new { Error = "InvalidNameFormat", Message = $"Invalid configuration name: '{body.Name}'. Only A–Z and '_' allowed." }),
56+
invalidValue => BadRequest(new { Error = "InvalidValueFormat", Message = $"Value '{body.Value}' is not a valid {body.Type}." })
57+
);
58+
}
59+
60+
/// <summary>
61+
/// Updates an existing configuration
62+
/// </summary>
63+
/// <response code="200">Configuration updated (or nothing to do)</response>
64+
/// <response code="401">Unauthorized</response>
65+
/// <response code="400">Invalid name or value format or type mismatch</response>
66+
/// <response code="404">Not found</response>
67+
[HttpPut("config")]
68+
public async Task<IActionResult> ConfigurationUpdate(
69+
[FromBody] ConfigurationUpdateItemRequest body,
70+
[FromServices] IConfigurationService configurationService,
71+
CancellationToken cancellationToken)
72+
{
73+
var result = await configurationService.TryUpdateItemAsync(
74+
body.Name,
75+
body.Description,
76+
body.Value
77+
);
78+
79+
return result.Match<IActionResult>(
80+
success => Ok(),
81+
notFound => NotFound(new { Error = "NotFound", Message = $"No configuration named '{body.Name}'." }),
82+
invalidName => BadRequest(new { Error = "InvalidNameFormat", Message = $"Invalid configuration name: '{body.Name}'." }),
83+
invalidValue => BadRequest(new { Error = "InvalidValueFormat", Message = $"Value '{body.Value}' is not a valid format for '{body.Name}'." }),
84+
invalidType => BadRequest(new { Error = "InvalidValueType", Message = $"Type mismatch when updating '{body.Name}'." })
85+
);
86+
}
87+
88+
/// <summary>
89+
/// Deletes a configuration
90+
/// </summary>
91+
/// <response code="200">Configuration deleted</response>
92+
/// <response code="401">Unauthorized</response>
93+
/// <response code="400">Invalid name format</response>
94+
/// <response code="404">Not found</response>
95+
[HttpDelete("config/{name}")]
96+
public async Task<IActionResult> ConfigurationDelete(
97+
string name,
98+
[FromServices] IConfigurationService configurationService,
99+
CancellationToken cancellationToken)
100+
{
101+
var result = await configurationService.TryDeleteItemAsync(name);
102+
103+
return result.Match<IActionResult>(
104+
success => Ok(),
105+
notFound => NotFound(new { Error = "NotFound", Message = $"No configuration named '{name}'." }),
106+
invalidName => BadRequest(new { Error = "InvalidNameFormat", Message = $"Invalid configuration name: '{name}'." })
107+
);
108+
}
109+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using OpenShock.Common.OpenShockDb;
2+
3+
namespace OpenShock.API.Controller.Admin.DTOs;
4+
5+
/// <summary>
6+
/// Request DTO for adding a new configuration item.
7+
/// </summary>
8+
public sealed class ConfigurationAddItemRequest
9+
{
10+
/// <summary>
11+
/// The unique name of the configuration item.
12+
/// Must consist of uppercase letters and underscores only.
13+
/// </summary>
14+
public required string Name { get; init; }
15+
16+
/// <summary>
17+
/// A human-readable description of the configuration item’s purpose.
18+
/// </summary>
19+
public required string Description { get; init; }
20+
21+
/// <summary>
22+
/// The data type of the configuration value.
23+
/// </summary>
24+
public required ConfigurationValueType Type { get; init; }
25+
26+
/// <summary>
27+
/// The initial value for the configuration item, serialized as a string.
28+
/// Must conform to the format required by <see cref="Type"/>.
29+
/// </summary>
30+
public required string Value { get; init; }
31+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using OpenShock.Common.OpenShockDb;
2+
3+
namespace OpenShock.API.Controller.Admin.DTOs;
4+
5+
/// <summary>
6+
/// Data transfer object representing a configuration item.
7+
/// </summary>
8+
public sealed class ConfigurationItemDto
9+
{
10+
/// <summary>
11+
/// The unique name of the configuration item.
12+
/// </summary>
13+
public required string Name { get; init; }
14+
15+
/// <summary>
16+
/// A human-readable description of what this configuration item controls.
17+
/// </summary>
18+
public required string Description { get; init; }
19+
20+
/// <summary>
21+
/// The data type of the configuration value.
22+
/// </summary>
23+
public required ConfigurationValueType Type { get; init; }
24+
25+
/// <summary>
26+
/// The current value of the configuration item, serialized as a string.
27+
/// </summary>
28+
public required string Value { get; init; }
29+
30+
/// <summary>
31+
/// The timestamp when the configuration was last updated.
32+
/// </summary>
33+
public required DateTimeOffset UpdatedAt { get; init; }
34+
35+
/// <summary>
36+
/// The timestamp when the configuration was originally created.
37+
/// </summary>
38+
public required DateTimeOffset CreatedAt { get; init; }
39+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using OpenShock.Common.OpenShockDb;
2+
using System.ComponentModel.DataAnnotations;
3+
4+
namespace OpenShock.API.Controller.Admin.DTOs;
5+
6+
/// <summary>
7+
/// Request DTO for updating a configuration item.
8+
/// Either <see cref="Description"/> or <see cref="Value"/> (or both) may be provided.
9+
/// If both are null, no update action is performed.
10+
/// </summary>
11+
public sealed class ConfigurationUpdateItemRequest
12+
{
13+
/// <summary>
14+
/// The name of the configuration item to update.
15+
/// Must consist only of uppercase letters and underscores.
16+
/// </summary>
17+
public required string Name { get; set; }
18+
19+
/// <summary>
20+
/// (Optional) New description for the configuration item.
21+
/// </summary>
22+
public string? Description { get; set; }
23+
24+
/// <summary>
25+
/// (Optional) New value for the configuration item.
26+
/// Must match the format expected by its existing <see cref="ConfigurationValueType"/>.
27+
/// </summary>
28+
public string? Value { get; set; }
29+
}

‎Common/Common.csproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
</PackageReference>
1515
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.7" />
1616
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.7" />
17+
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.7.0" />
1718
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
1819
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
1920
<PackageReference Include="NRedisStack" Version="1.0.0" />

0 commit comments

Comments
 (0)