Adding Flags API
This commit is contained in:
47
src/api/MicCheck.Api/Identities/IdentitiesController.cs
Normal file
47
src/api/MicCheck.Api/Identities/IdentitiesController.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using MicCheck.Api.Authorization;
|
||||
using MicCheck.Api.Features;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace MicCheck.Api.Identities;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/v1/identities")]
|
||||
[Authorize(Policy = AuthorizationPolicies.FlagsApiAccess)]
|
||||
public class IdentitiesController(
|
||||
FeatureEvaluationService featureEvaluationService,
|
||||
IdentityResolutionService identityResolutionService) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IdentityResponse>> GetByIdentifier(
|
||||
[FromQuery] string identifier, CancellationToken ct)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(identifier))
|
||||
return BadRequest("identifier query parameter is required.");
|
||||
|
||||
var environmentId = int.Parse(User.FindFirst("EnvironmentId")!.Value!);
|
||||
|
||||
var identity = await identityResolutionService.ResolveAsync(environmentId, identifier, null, ct);
|
||||
var flags = await featureEvaluationService.EvaluateForIdentityAsync(environmentId, identifier, null, ct);
|
||||
|
||||
return Ok(new IdentityResponse(
|
||||
identity.Traits.Select(TraitResponse.From).ToList(),
|
||||
flags.Select(FlagResponse.From).ToList()));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<IdentityResponse>> Identify(
|
||||
[FromBody] IdentityRequest request, CancellationToken ct)
|
||||
{
|
||||
var environmentId = int.Parse(User.FindFirst("EnvironmentId")!.Value!);
|
||||
|
||||
var identity = await identityResolutionService.ResolveAsync(
|
||||
environmentId, request.Identifier, request.Traits, ct);
|
||||
var flags = await featureEvaluationService.EvaluateForIdentityAsync(
|
||||
environmentId, request.Identifier, request.Traits, ct);
|
||||
|
||||
return Ok(new IdentityResponse(
|
||||
identity.Traits.Select(TraitResponse.From).ToList(),
|
||||
flags.Select(FlagResponse.From).ToList()));
|
||||
}
|
||||
}
|
||||
3
src/api/MicCheck.Api/Identities/IdentityRequest.cs
Normal file
3
src/api/MicCheck.Api/Identities/IdentityRequest.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace MicCheck.Api.Identities;
|
||||
|
||||
public record IdentityRequest(string Identifier, IReadOnlyList<TraitInput>? Traits);
|
||||
58
src/api/MicCheck.Api/Identities/IdentityResolutionService.cs
Normal file
58
src/api/MicCheck.Api/Identities/IdentityResolutionService.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using MicCheck.Api.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace MicCheck.Api.Identities;
|
||||
|
||||
public class IdentityResolutionService(MicCheckDbContext db)
|
||||
{
|
||||
public async Task<Identity> ResolveAsync(
|
||||
int environmentId,
|
||||
string identifier,
|
||||
IReadOnlyList<TraitInput>? traits,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var identity = await db.Identities
|
||||
.Include(i => i.Traits)
|
||||
.FirstOrDefaultAsync(i => i.EnvironmentId == environmentId && i.Identifier == identifier, ct);
|
||||
|
||||
if (identity is null)
|
||||
{
|
||||
identity = new Identity
|
||||
{
|
||||
Identifier = identifier,
|
||||
EnvironmentId = environmentId,
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
db.Identities.Add(identity);
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
if (traits is { Count: > 0 })
|
||||
{
|
||||
foreach (var traitInput in traits)
|
||||
{
|
||||
var existing = identity.Traits.FirstOrDefault(t => t.Key == traitInput.TraitKey);
|
||||
if (existing is not null)
|
||||
{
|
||||
existing.Value = traitInput.GetStringValue();
|
||||
existing.ValueType = traitInput.GetValueType();
|
||||
}
|
||||
else
|
||||
{
|
||||
var newTrait = new IdentityTrait
|
||||
{
|
||||
IdentityId = identity.Id,
|
||||
Key = traitInput.TraitKey,
|
||||
Value = traitInput.GetStringValue(),
|
||||
ValueType = traitInput.GetValueType()
|
||||
};
|
||||
db.IdentityTraits.Add(newTrait);
|
||||
identity.Traits.Add(newTrait);
|
||||
}
|
||||
}
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
return identity;
|
||||
}
|
||||
}
|
||||
7
src/api/MicCheck.Api/Identities/IdentityResponse.cs
Normal file
7
src/api/MicCheck.Api/Identities/IdentityResponse.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using MicCheck.Api.Features;
|
||||
|
||||
namespace MicCheck.Api.Identities;
|
||||
|
||||
public record IdentityResponse(
|
||||
IReadOnlyList<TraitResponse> Traits,
|
||||
IReadOnlyList<FlagResponse> Flags);
|
||||
31
src/api/MicCheck.Api/Identities/TraitInput.cs
Normal file
31
src/api/MicCheck.Api/Identities/TraitInput.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MicCheck.Api.Identities;
|
||||
|
||||
public record TraitInput(
|
||||
[property: JsonPropertyName("trait_key")] string TraitKey,
|
||||
[property: JsonPropertyName("trait_value")] object? TraitValue)
|
||||
{
|
||||
public string GetStringValue() => TraitValue switch
|
||||
{
|
||||
JsonElement el when el.ValueKind == JsonValueKind.String => el.GetString() ?? string.Empty,
|
||||
JsonElement el when el.ValueKind == JsonValueKind.True => "true",
|
||||
JsonElement el when el.ValueKind == JsonValueKind.False => "false",
|
||||
JsonElement el when el.ValueKind == JsonValueKind.Null => string.Empty,
|
||||
JsonElement el => el.ToString(),
|
||||
null => string.Empty,
|
||||
_ => TraitValue.ToString() ?? string.Empty
|
||||
};
|
||||
|
||||
public TraitValueType GetValueType() => TraitValue switch
|
||||
{
|
||||
JsonElement el when el.ValueKind is JsonValueKind.True or JsonValueKind.False => TraitValueType.Boolean,
|
||||
JsonElement el when el.ValueKind == JsonValueKind.Number && el.TryGetInt64(out _) => TraitValueType.Integer,
|
||||
JsonElement el when el.ValueKind == JsonValueKind.Number => TraitValueType.Float,
|
||||
bool => TraitValueType.Boolean,
|
||||
int or long => TraitValueType.Integer,
|
||||
float or double or decimal => TraitValueType.Float,
|
||||
_ => TraitValueType.String
|
||||
};
|
||||
}
|
||||
7
src/api/MicCheck.Api/Identities/TraitResponse.cs
Normal file
7
src/api/MicCheck.Api/Identities/TraitResponse.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace MicCheck.Api.Identities;
|
||||
|
||||
public record TraitResponse(string TraitKey, string TraitValue)
|
||||
{
|
||||
public static TraitResponse From(IdentityTrait trait) =>
|
||||
new(trait.Key, trait.Value);
|
||||
}
|
||||
Reference in New Issue
Block a user