WIP
CI / build-and-push (push) Has been cancelled
CI / deploy-qa (push) Has been cancelled
CI / smoke-qa (push) Has been cancelled

This commit is contained in:
James Wampler
2026-07-22 14:02:59 -07:00
parent 3eebef56a0
commit aa611feaff
22 changed files with 114 additions and 52 deletions
@@ -1,5 +1,8 @@
using MicCheck.Api.Common.Security.Authorization;
using MicCheck.Api.Common;
using MicCheck.Api.Features;
using MicCheck.Api.Identities;
using MicCheck.Api.Segments;
using MicCheck.Api.Webhooks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -146,9 +149,9 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
}
[HttpGet("api/v1/environment/{apiKey}/identities")]
public async Task<ActionResult<PaginatedResponse<Identities.AdminIdentityResponse>>> ListIdentities(
public async Task<ActionResult<PaginatedResponse<AdminIdentityResponse>>> ListIdentities(
string apiKey,
[FromServices] Identities.AdminIdentityService adminIdentityService,
[FromServices] AdminIdentityService adminIdentityService,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
CancellationToken ct = default)
@@ -158,15 +161,15 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
if (environment is null) return NotFound();
var result = await adminIdentityService.ListAsync(environment.Id, page, pageSize, ct);
var results = result.Items.Select(Identities.AdminIdentityResponse.From).ToList();
return Ok(new PaginatedResponse<Identities.AdminIdentityResponse>(result.Total, null, null, results));
var results = result.Items.Select(AdminIdentityResponse.From).ToList();
return Ok(new PaginatedResponse<AdminIdentityResponse>(result.Total, null, null, results));
}
[HttpPost("api/v1/environment/{apiKey}/identities")]
public async Task<ActionResult<Identities.AdminIdentityResponse>> CreateIdentity(
public async Task<ActionResult<AdminIdentityResponse>> CreateIdentity(
string apiKey,
[FromBody] Identities.CreateIdentityRequest request,
[FromServices] Identities.AdminIdentityService adminIdentityService,
[FromBody] CreateIdentityRequest request,
[FromServices] AdminIdentityService adminIdentityService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -177,13 +180,13 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
return Conflict(new { error = "An identity with this identifier already exists in the environment." });
return CreatedAtAction(nameof(GetIdentity), new { apiKey, id = identity.Id },
Identities.AdminIdentityResponse.From(identity));
AdminIdentityResponse.From(identity));
}
[HttpGet("api/v1/environment/{apiKey}/identity/{id}")]
public async Task<ActionResult<Identities.AdminIdentityResponse>> GetIdentity(
public async Task<ActionResult<AdminIdentityResponse>> GetIdentity(
string apiKey, int id,
[FromServices] Identities.AdminIdentityService adminIdentityService,
[FromServices] AdminIdentityService adminIdentityService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -191,13 +194,13 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
var identity = await adminIdentityService.FindByIdAsync(id, environment.Id, ct);
if (identity is null) return NotFound();
return Ok(Identities.AdminIdentityResponse.From(identity));
return Ok(AdminIdentityResponse.From(identity));
}
[HttpDelete("api/v1/environment/{apiKey}/identity/{id}")]
public async Task<IActionResult> DeleteIdentity(
string apiKey, int id,
[FromServices] Identities.AdminIdentityService adminIdentityService,
[FromServices] AdminIdentityService adminIdentityService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -211,10 +214,10 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
}
[HttpPut("api/v1/environment/{apiKey}/identity/{id}/trait/{key}")]
public async Task<ActionResult<Identities.TraitResponse>> UpsertIdentityTrait(
public async Task<ActionResult<TraitResponse>> UpsertIdentityTrait(
string apiKey, int id, string key,
[FromBody] Identities.UpsertTraitRequest request,
[FromServices] Identities.AdminIdentityService adminIdentityService,
[FromBody] UpsertTraitRequest request,
[FromServices] AdminIdentityService adminIdentityService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -228,7 +231,7 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
[HttpDelete("api/v1/environment/{apiKey}/identity/{id}/trait/{key}")]
public async Task<IActionResult> DeleteIdentityTrait(
string apiKey, int id, string key,
[FromServices] Identities.AdminIdentityService adminIdentityService,
[FromServices] AdminIdentityService adminIdentityService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -240,9 +243,9 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
}
[HttpGet("api/v1/environment/{apiKey}/identity/{id}/featurestates")]
public async Task<ActionResult<IReadOnlyList<Features.FeatureStateResponse>>> GetIdentityFeatureStates(
public async Task<ActionResult<IReadOnlyList<FeatureStateResponse>>> GetIdentityFeatureStates(
string apiKey, int id,
[FromServices] Identities.AdminIdentityService adminIdentityService,
[FromServices] AdminIdentityService adminIdentityService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -252,14 +255,14 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
if (identity is null) return NotFound();
var states = await adminIdentityService.GetFeatureStatesAsync(id, environment.Id, ct);
return Ok(states.Select(Features.FeatureStateResponse.From).ToList());
return Ok(states.Select(FeatureStateResponse.From).ToList());
}
[HttpPut("api/v1/environment/{apiKey}/identity/{id}/featurestate/{featureId}")]
public async Task<ActionResult<Features.FeatureStateResponse>> SetIdentityFeatureState(
public async Task<ActionResult<FeatureStateResponse>> SetIdentityFeatureState(
string apiKey, int id, int featureId,
Features.UpdateFeatureStateRequest request,
[FromServices] Identities.AdminIdentityService adminIdentityService,
UpdateFeatureStateRequest request,
[FromServices] AdminIdentityService adminIdentityService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -269,13 +272,13 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
if (identity is null) return NotFound();
var state = await adminIdentityService.SetFeatureStateAsync(id, environment.Id, featureId, request.Enabled, request.Value, ct);
return Ok(Features.FeatureStateResponse.From(state));
return Ok(FeatureStateResponse.From(state));
}
[HttpDelete("api/v1/environment/{apiKey}/identity/{id}/featurestate/{featureId}")]
public async Task<IActionResult> DeleteIdentityFeatureState(
string apiKey, int id, int featureId,
[FromServices] Identities.AdminIdentityService adminIdentityService,
[FromServices] AdminIdentityService adminIdentityService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -289,22 +292,22 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
}
[HttpGet("api/v1/environment/{apiKey}/featurestates")]
public async Task<ActionResult<IReadOnlyList<Features.FeatureStateResponse>>> ListFeatureStates(
public async Task<ActionResult<IReadOnlyList<FeatureStateResponse>>> ListFeatureStates(
string apiKey,
[FromServices] Features.FeatureStateService featureStateService,
[FromServices] FeatureStateService featureStateService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
if (environment is null) return NotFound();
var states = await featureStateService.ListByEnvironmentAsync(environment.Id, ct);
return Ok(states.Select(Features.FeatureStateResponse.From).ToList());
return Ok(states.Select(FeatureStateResponse.From).ToList());
}
[HttpGet("api/v1/environment/{apiKey}/featurestate/{id}")]
public async Task<ActionResult<Features.FeatureStateResponse>> GetFeatureState(
public async Task<ActionResult<FeatureStateResponse>> GetFeatureState(
string apiKey, int id,
[FromServices] Features.FeatureStateService featureStateService,
[FromServices] FeatureStateService featureStateService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -312,14 +315,14 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
var state = await featureStateService.FindByIdAsync(id, environment.Id, ct);
if (state is null) return NotFound();
return Ok(Features.FeatureStateResponse.From(state));
return Ok(FeatureStateResponse.From(state));
}
[HttpPut("api/v1/environment/{apiKey}/featurestate/{id}")]
public async Task<ActionResult<Features.FeatureStateResponse>> UpdateFeatureState(
public async Task<ActionResult<FeatureStateResponse>> UpdateFeatureState(
string apiKey, int id,
Features.UpdateFeatureStateRequest request,
[FromServices] Features.FeatureStateService featureStateService,
UpdateFeatureStateRequest request,
[FromServices] FeatureStateService featureStateService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -329,14 +332,14 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
if (state is null) return NotFound();
var updated = await featureStateService.UpdateAsync(id, request.Enabled, request.Value, ct);
return Ok(Features.FeatureStateResponse.From(updated));
return Ok(FeatureStateResponse.From(updated));
}
[HttpPatch("api/v1/environment/{apiKey}/featurestate/{id}")]
public async Task<ActionResult<Features.FeatureStateResponse>> PatchFeatureState(
public async Task<ActionResult<FeatureStateResponse>> PatchFeatureState(
string apiKey, int id,
Features.PatchFeatureStateRequest request,
[FromServices] Features.FeatureStateService featureStateService,
PatchFeatureStateRequest request,
[FromServices] FeatureStateService featureStateService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -346,15 +349,15 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
if (state is null) return NotFound();
var updated = await featureStateService.PatchAsync(id, request.Enabled, request.Value, ct);
return Ok(Features.FeatureStateResponse.From(updated));
return Ok(FeatureStateResponse.From(updated));
}
// ─── Feature Segments ────────────────────────────────────────────────────
[HttpGet("api/v1/environment/{apiKey}/feature/{featureId}/segments")]
public async Task<ActionResult<IReadOnlyList<Features.FeatureSegmentResponse>>> ListFeatureSegments(
public async Task<ActionResult<IReadOnlyList<FeatureSegmentResponse>>> ListFeatureSegments(
string apiKey, int featureId,
[FromServices] Features.FeatureSegmentService featureSegmentService,
[FromServices] FeatureSegmentService featureSegmentService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -365,10 +368,10 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
}
[HttpPost("api/v1/environment/{apiKey}/feature/{featureId}/segments")]
public async Task<ActionResult<Features.FeatureSegmentResponse>> CreateFeatureSegment(
public async Task<ActionResult<FeatureSegmentResponse>> CreateFeatureSegment(
string apiKey, int featureId,
Features.CreateFeatureSegmentRequest request,
[FromServices] Features.FeatureSegmentService featureSegmentService,
CreateFeatureSegmentRequest request,
[FromServices] FeatureSegmentService featureSegmentService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -388,10 +391,10 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
}
[HttpPut("api/v1/environment/{apiKey}/feature/{featureId}/segment/{id}")]
public async Task<ActionResult<Features.FeatureSegmentResponse>> UpdateFeatureSegment(
public async Task<ActionResult<FeatureSegmentResponse>> UpdateFeatureSegment(
string apiKey, int featureId, int id,
Features.UpdateFeatureSegmentRequest request,
[FromServices] Features.FeatureSegmentService featureSegmentService,
UpdateFeatureSegmentRequest request,
[FromServices] FeatureSegmentService featureSegmentService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -405,7 +408,7 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
[HttpDelete("api/v1/environment/{apiKey}/feature/{featureId}/segment/{id}")]
public async Task<IActionResult> DeleteFeatureSegment(
string apiKey, int featureId, int id,
[FromServices] Features.FeatureSegmentService featureSegmentService,
[FromServices] FeatureSegmentService featureSegmentService,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -418,11 +421,11 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
// ─── Identity Segments ───────────────────────────────────────────────────
[HttpGet("api/v1/environment/{apiKey}/identity/{id}/segments")]
public async Task<ActionResult<IReadOnlyList<Segments.SegmentSummaryResponse>>> GetIdentitySegments(
public async Task<ActionResult<IReadOnlyList<SegmentSummaryResponse>>> GetIdentitySegments(
string apiKey, int id,
[FromServices] Identities.AdminIdentityService adminIdentityService,
[FromServices] Segments.SegmentService segmentService,
[FromServices] Segments.SegmentEvaluator segmentEvaluator,
[FromServices] AdminIdentityService adminIdentityService,
[FromServices] SegmentService segmentService,
[FromServices] SegmentEvaluator segmentEvaluator,
CancellationToken ct)
{
var environment = await environmentService.FindByApiKeyAsync(apiKey, ct);
@@ -434,7 +437,7 @@ public class EnvironmentsController(EnvironmentService environmentService, Webho
var segments = await segmentService.ListByProjectAsync(environment.ProjectId, ct);
var matching = segments
.Where(s => segmentEvaluator.Evaluate(s, identity.Traits.ToList(), identity.Identifier))
.Select(s => new Segments.SegmentSummaryResponse(s.Id, s.Name))
.Select(s => new SegmentSummaryResponse(s.Id, s.Name))
.ToList();
return Ok(matching);