Relay all api calls
This commit is contained in:
parent
d951bd625a
commit
9d9c9b3f38
30 changed files with 699 additions and 42 deletions
107
.vscode/tasks.json
vendored
Normal file
107
.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Start: Postgres",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "docker compose up -d",
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
"group": "build",
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"panel": "dedicated",
|
||||||
|
"group": "dev"
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Start: Registration.API",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "dotnet run --project src/Registration/Registration.API --launch-profile https",
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
"isBackground": true,
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"panel": "dedicated",
|
||||||
|
"group": "dev"
|
||||||
|
},
|
||||||
|
"problemMatcher": {
|
||||||
|
"pattern": {
|
||||||
|
"regexp": "^$"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": "Building\\.\\.\\.",
|
||||||
|
"endsPattern": "Now listening on:"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Start: AuthAPI",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "dotnet run --project src/Auth/AuthAPI",
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
"isBackground": true,
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"panel": "dedicated",
|
||||||
|
"group": "dev"
|
||||||
|
},
|
||||||
|
"problemMatcher": {
|
||||||
|
"pattern": {
|
||||||
|
"regexp": "^$"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": "Building\\.\\.\\.",
|
||||||
|
"endsPattern": "Now listening on:"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Start: Frontend",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "npm run dev",
|
||||||
|
"options": {
|
||||||
|
"cwd": "${workspaceFolder}/src/Web/lan-frontend"
|
||||||
|
},
|
||||||
|
"isBackground": true,
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always",
|
||||||
|
"panel": "dedicated",
|
||||||
|
"group": "dev"
|
||||||
|
},
|
||||||
|
"problemMatcher": {
|
||||||
|
"pattern": {
|
||||||
|
"regexp": "^$"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"activeOnStart": true,
|
||||||
|
"beginsPattern": ".*",
|
||||||
|
"endsPattern": "Local:.*localhost"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Start: All",
|
||||||
|
"dependsOn": [
|
||||||
|
"Start: Postgres",
|
||||||
|
"Start: Registration.API",
|
||||||
|
"Start: AuthAPI",
|
||||||
|
"Start: Frontend"
|
||||||
|
],
|
||||||
|
"dependsOrder": "sequence",
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"problemMatcher": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
89
README.md
89
README.md
|
|
@ -23,3 +23,92 @@ Micro Service For Registering For An Event
|
||||||
### Database
|
### Database
|
||||||
|
|
||||||
- Postgres
|
- Postgres
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Sensitive values are never stored in source control. Configure them via [user secrets](https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets) (development) or environment variables (production).
|
||||||
|
|
||||||
|
Key-only examples are available here:
|
||||||
|
|
||||||
|
- `src/Registration/Registration.API/appsettings.example.json`
|
||||||
|
- `src/Auth/AuthAPI/appsettings.example.json`
|
||||||
|
- `src/Web/lan-frontend/.env.example`
|
||||||
|
|
||||||
|
### Frontend (Next.js)
|
||||||
|
|
||||||
|
Run from `src/Web/lan-frontend`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env.local
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the backend API base URL in `.env.local`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
REGISTRATION_API_URL=http://localhost:5063
|
||||||
|
```
|
||||||
|
|
||||||
|
### Registration.API
|
||||||
|
|
||||||
|
Run from `src/Registration/Registration.API`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet user-secrets set "VbytesRelay:ApiKey" "<your-api-key>"
|
||||||
|
dotnet user-secrets set "VbytesRelay:ClientCertificatePfxPath" "<absolute-path-to>.pfx"
|
||||||
|
dotnet user-secrets set "VbytesRelay:ParticipantRegisterPath" "/api/participant"
|
||||||
|
dotnet user-secrets set "Security:SsnPepper" "<your-pepper-value>"
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Description |
|
||||||
|
| -------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||||
|
| `VbytesRelay:ApiKey` | API key for the Vbytes relay service |
|
||||||
|
| `VbytesRelay:ClientCertificatePfxPath` | Absolute path to the client certificate `.pfx` file |
|
||||||
|
| `VbytesRelay:ParticipantRegisterPath` | Path for the participant registration endpoint on the relay (e.g. `/api/participant`) |
|
||||||
|
| `Security:SsnPepper` | Pepper value used when hashing SSNs |
|
||||||
|
|
||||||
|
For non-development environments, ensure these environment variables are set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ConnectionStrings__DefaultConnection=<postgres-connection-string>
|
||||||
|
Security__SsnPepper=<pepper-value>
|
||||||
|
VbytesRelay__BaseUrl=https://api.lan.vbytes.se
|
||||||
|
VbytesRelay__ParticipantRegisterPath=/api/participant
|
||||||
|
VbytesRelay__VolunteerRegisterPath=/api/volunteer
|
||||||
|
VbytesRelay__ApiKeyHeaderName=X-Api-Key
|
||||||
|
VbytesRelay__ApiKey=<relay-api-key>
|
||||||
|
VbytesRelay__ClientCertificatePfxPath=<absolute-path-to>.pfx
|
||||||
|
AuthApi__BaseUrl=<auth-api-base-url>
|
||||||
|
AuthApi__ValidatePath=/validate
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** ASP.NET Core's configuration priority is `appsettings.json` → `appsettings.Development.json` → **user secrets** → **environment variables**. Environment variables always win, so any value set in `launchSettings.json` will override user secrets. Secret values (`ApiKey`, cert paths, relay paths) are intentionally absent from `launchSettings.json` to ensure user secrets are respected.
|
||||||
|
|
||||||
|
### AuthAPI
|
||||||
|
|
||||||
|
Run from `src/Auth/AuthAPI`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet user-secrets set "EnvironmentVariables:ApiUrl" "<member-api-url>"
|
||||||
|
dotnet user-secrets set "EnvironmentVariables:ApiKey" "<member-api-key>"
|
||||||
|
dotnet user-secrets set "EnvironmentVariables:AssociationNumber" "<association-number>"
|
||||||
|
```
|
||||||
|
|
||||||
|
| Key | Description |
|
||||||
|
| ---------------------------------------- | ------------------------------------------ |
|
||||||
|
| `EnvironmentVariables:ApiUrl` | Base URL of the member validation API |
|
||||||
|
| `EnvironmentVariables:ApiKey` | API key for the member validation API |
|
||||||
|
| `EnvironmentVariables:AssociationNumber` | Association number used for member lookups |
|
||||||
|
|
||||||
|
For non-development environments, ensure these environment variables are set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
EnvironmentVariables__ApiUrl=<member-api-url>
|
||||||
|
EnvironmentVariables__ApiKey=<member-api-key>
|
||||||
|
EnvironmentVariables__AssociationNumber=<association-number>
|
||||||
|
```
|
||||||
|
|
||||||
|
For frontend production configuration, set:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
REGISTRATION_API_URL=<registration-api-base-url>
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>e53e6e14-fe65-43c6-82fd-0f1fb394679d</UserSecretsId>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,22 @@
|
||||||
@AuthAPI_HostAddress = http://localhost:5127
|
@AuthAPI_HostAddress = http://localhost:5127
|
||||||
|
|
||||||
GET {{AuthAPI_HostAddress}}/validate/
|
# Validate by SSN
|
||||||
Accept: application/json
|
POST {{AuthAPI_HostAddress}}/validate
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"Email": "someValue",
|
"ssn": "8612125643"
|
||||||
"FirstName": "name"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
GET {{AuthAPI_HostAddress}}/validate/
|
# Validate by email + first name
|
||||||
Accept: application/json
|
POST {{AuthAPI_HostAddress}}/validate
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"Ssn": "10 or 8 number length"
|
"email": "testwith@validemail.se",
|
||||||
|
"firstName": "AndCorrectName"
|
||||||
}
|
}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
@ -5,7 +5,7 @@ using AuthAPI;
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
builder.Services.Configure<AuthSettings>(
|
builder.Services.Configure<AuthSettings>(
|
||||||
builder.Configuration.GetSection("EnviromentVariables"));
|
builder.Configuration.GetSection("EnvironmentVariables"));
|
||||||
builder.Services.AddScoped<MemberValidationService>();
|
builder.Services.AddScoped<MemberValidationService>();
|
||||||
builder.Services.ConfigureHttpJsonOptions(options =>
|
builder.Services.ConfigureHttpJsonOptions(options =>
|
||||||
{
|
{
|
||||||
|
|
@ -14,7 +14,7 @@ builder.Services.ConfigureHttpJsonOptions(options =>
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
app.MapGet("/validate", async (
|
app.MapPost("/validate", async (
|
||||||
[FromBody] Request validationRequest,
|
[FromBody] Request validationRequest,
|
||||||
HttpClient httpClient,
|
HttpClient httpClient,
|
||||||
MemberValidationService memberService,
|
MemberValidationService memberService,
|
||||||
|
|
@ -32,7 +32,8 @@ app.MapGet("/validate", async (
|
||||||
return Results.StatusCode((int)response.StatusCode);
|
return Results.StatusCode((int)response.StatusCode);
|
||||||
|
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
var content = await response.Content.ReadAsStringAsync();
|
||||||
return content.Contains("\"member_found\":true,") ? Results.Ok() : Results.NotFound();
|
Console.WriteLine($"[DEBUG] Sverok response: {content}");
|
||||||
|
return content.Contains("\"member_found\":true") ? Results.Ok() : Results.NotFound();
|
||||||
})
|
})
|
||||||
.WithName("ValidateMember");
|
.WithName("ValidateMember");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"EnviromentVariables": {
|
"EnvironmentVariables": {
|
||||||
"ApiUrl": "",
|
"ApiUrl": "",
|
||||||
"ApiKey": "",
|
"ApiKey": "",
|
||||||
"AssociationNumber": ""
|
"AssociationNumber": ""
|
||||||
|
|
|
||||||
14
src/Auth/AuthAPI/appsettings.example.json
Normal file
14
src/Auth/AuthAPI/appsettings.example.json
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"EnvironmentVariables": {
|
||||||
|
"ApiUrl": "",
|
||||||
|
"ApiKey": "",
|
||||||
|
"AssociationNumber": ""
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "",
|
||||||
|
"Microsoft.AspNetCore": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": ""
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"EnviromentVariables": {
|
"EnvironmentVariables": {
|
||||||
"ApiUrl": "",
|
"ApiUrl": "",
|
||||||
"ApiKey": "",
|
"ApiKey": "",
|
||||||
"AssociationNumber": ""
|
"AssociationNumber": ""
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Registration.API.Configuration;
|
||||||
|
|
||||||
|
public class AuthApiOptions
|
||||||
|
{
|
||||||
|
public string BaseUrl { get; set; } = string.Empty;
|
||||||
|
public string ValidatePath { get; set; } = "/validate";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
namespace Registration.API.Configuration;
|
||||||
|
|
||||||
|
public class VbytesRelayOptions
|
||||||
|
{
|
||||||
|
public string BaseUrl { get; set; } = string.Empty;
|
||||||
|
public string ParticipantRegisterPath { get; set; } = string.Empty;
|
||||||
|
public string ApiKeyHeaderName { get; set; } = "X-Api-Key";
|
||||||
|
public string ApiKey { get; set; } = string.Empty;
|
||||||
|
public string ClientCertificatePfxPath { get; set; } = string.Empty;
|
||||||
|
public string VolunteerRegisterPath { get; set; } = "/api/volunteer/register";
|
||||||
|
}
|
||||||
|
|
@ -1,16 +1,26 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Registration.API.Services;
|
||||||
using Registration.Domain.Models;
|
using Registration.Domain.Models;
|
||||||
|
|
||||||
namespace Registration.API.Controllers
|
namespace Registration.API.Controllers
|
||||||
{
|
{
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class ParticipantController : ControllerBase
|
public class ParticipantController(IVbytesParticipantRelayService relayService) : ControllerBase
|
||||||
{
|
{
|
||||||
|
private readonly IVbytesParticipantRelayService _relayService = relayService;
|
||||||
|
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
public IActionResult RegisterForLan([FromBody] Participant participant)
|
public async Task<IActionResult> RegisterForLan([FromBody] Participant participant, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var result = await _relayService.RegisterParticipantAsync(participant, cancellationToken);
|
||||||
|
|
||||||
|
if (result.Success)
|
||||||
{
|
{
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return StatusCode(result.StatusCode, new { message = result.Message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,21 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Registration.API.Services;
|
||||||
using Registration.Infra.Repositories;
|
using Registration.Infra.Repositories;
|
||||||
|
|
||||||
namespace Registration.API.Controllers
|
namespace Registration.API.Controllers
|
||||||
{
|
{
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class RegistrationController(IMemberRepository memberRepository) : ControllerBase
|
public class RegistrationController(IMemberRepository memberRepository, IAuthService authService) : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IMemberRepository _memberRepository = memberRepository;
|
private readonly IMemberRepository _memberRepository = memberRepository;
|
||||||
|
private readonly IAuthService _authService = authService;
|
||||||
|
|
||||||
[HttpGet("register/{ssn}")]
|
[HttpGet("register/{ssn}")]
|
||||||
public IActionResult ValidateSsn(string ssn)
|
public async Task<IActionResult> ValidateSsn(string ssn, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Should talk to the auth api to validate the ssn properly.
|
var isMember = await _authService.IsMemberAsync(ssn, cancellationToken);
|
||||||
if (ssn.Length == 10 && long.TryParse(ssn, out _))
|
return isMember ? Ok() : NotFound();
|
||||||
{
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("register/{ssn}")]
|
[HttpPost("register/{ssn}")]
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,26 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Registration.API.Services;
|
||||||
using Registration.Domain.Models;
|
using Registration.Domain.Models;
|
||||||
|
|
||||||
namespace Registration.API.Controllers
|
namespace Registration.API.Controllers
|
||||||
{
|
{
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class VolunteerController : ControllerBase
|
public class VolunteerController(IVbytesVolunteerRelayService relayService) : ControllerBase
|
||||||
{
|
{
|
||||||
|
private readonly IVbytesVolunteerRelayService _relayService = relayService;
|
||||||
|
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
public IActionResult RegisterVolunteer([FromBody] Volunteer volunteer)
|
public async Task<IActionResult> RegisterVolunteer([FromBody] Volunteer volunteer, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var result = await _relayService.RegisterVolunteerAsync(volunteer, cancellationToken);
|
||||||
|
|
||||||
|
if (result.Success)
|
||||||
{
|
{
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return StatusCode(result.StatusCode, new { message = result.Message });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Registration.API.DtoModels;
|
||||||
|
|
||||||
|
public record ParticipantRelayDto(
|
||||||
|
[property: JsonPropertyName("member")] bool Member,
|
||||||
|
[property: JsonPropertyName("first_name")] string FirstName,
|
||||||
|
[property: JsonPropertyName("surname")] string Surname,
|
||||||
|
[property: JsonPropertyName("grade")] string Grade,
|
||||||
|
[property: JsonPropertyName("phone")] string? Phone,
|
||||||
|
[property: JsonPropertyName("email")] string? Email,
|
||||||
|
[property: JsonPropertyName("guardian_name")] string GuardianName,
|
||||||
|
[property: JsonPropertyName("guardian_phone")] string GuardianPhone,
|
||||||
|
[property: JsonPropertyName("guardian_email")] string GuardianEmail,
|
||||||
|
[property: JsonPropertyName("is_visiting")] bool IsVisiting,
|
||||||
|
[property: JsonPropertyName("gdpr")] bool Gdpr,
|
||||||
|
[property: JsonPropertyName("friends")] string? Friends,
|
||||||
|
[property: JsonPropertyName("special_diet")] string? SpecialDiet
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Registration.API.DtoModels;
|
||||||
|
|
||||||
|
public record VolunteerRelayDto(
|
||||||
|
[property: JsonPropertyName("first_name")] string FirstName,
|
||||||
|
[property: JsonPropertyName("surname")] string Surname,
|
||||||
|
[property: JsonPropertyName("phone")] string Phone,
|
||||||
|
[property: JsonPropertyName("email")] string Email,
|
||||||
|
[property: JsonPropertyName("gdpr")] bool Gdpr,
|
||||||
|
[property: JsonPropertyName("areas")] string[] Areas
|
||||||
|
);
|
||||||
|
|
@ -1,10 +1,49 @@
|
||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using Registration.Infra.Repositories;
|
using Registration.Infra.Repositories;
|
||||||
|
using Registration.API.Configuration;
|
||||||
|
using Registration.API.Services;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
builder.Services.AddScoped<IMemberRepository, MemberRepository>();
|
builder.Services.AddScoped<IMemberRepository, MemberRepository>();
|
||||||
|
|
||||||
|
var relayOptions = builder.Configuration.GetSection("VbytesRelay").Get<VbytesRelayOptions>()
|
||||||
|
?? throw new InvalidOperationException("VbytesRelay configuration section is missing.");
|
||||||
|
|
||||||
|
builder.Services.Configure<VbytesRelayOptions>(builder.Configuration.GetSection("VbytesRelay"));
|
||||||
|
|
||||||
|
|
||||||
|
var certificate = X509CertificateLoader.LoadPkcs12FromFile(
|
||||||
|
relayOptions.ClientCertificatePfxPath, password: null);
|
||||||
|
|
||||||
|
builder.Services.AddHttpClient(VbytesParticipantRelayService.HttpClientName, client =>
|
||||||
|
{
|
||||||
|
client.BaseAddress = new Uri(relayOptions.BaseUrl);
|
||||||
|
client.Timeout = TimeSpan.FromSeconds(30);
|
||||||
|
})
|
||||||
|
.ConfigurePrimaryHttpMessageHandler(() =>
|
||||||
|
{
|
||||||
|
var handler = new HttpClientHandler();
|
||||||
|
handler.ClientCertificates.Add(certificate);
|
||||||
|
return handler;
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddScoped<IVbytesParticipantRelayService, VbytesParticipantRelayService>();
|
||||||
|
builder.Services.AddScoped<IVbytesVolunteerRelayService, VbytesVolunteerRelayService>();
|
||||||
|
|
||||||
|
var authApiOptions = builder.Configuration.GetSection("AuthApi").Get<AuthApiOptions>()
|
||||||
|
?? throw new InvalidOperationException("AuthApi configuration section is missing.");
|
||||||
|
|
||||||
|
builder.Services.Configure<AuthApiOptions>(builder.Configuration.GetSection("AuthApi"));
|
||||||
|
builder.Services.AddHttpClient(AuthService.HttpClientName, client =>
|
||||||
|
{
|
||||||
|
client.BaseAddress = new Uri(authApiOptions.BaseUrl);
|
||||||
|
client.Timeout = TimeSpan.FromSeconds(10);
|
||||||
|
});
|
||||||
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
@ -22,7 +61,11 @@ if (app.Environment.IsDevelopment())
|
||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
}
|
||||||
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@
|
||||||
"launchBrowser": false,
|
"launchBrowser": false,
|
||||||
"applicationUrl": "http://localhost:5063",
|
"applicationUrl": "http://localhost:5063",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"VbytesRelay__BaseUrl": "https://api.lan.vbytes.se",
|
||||||
|
"VbytesRelay__ApiKeyHeaderName": "X-Api-Key"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"https": {
|
"https": {
|
||||||
|
|
@ -16,7 +18,9 @@
|
||||||
"launchBrowser": false,
|
"launchBrowser": false,
|
||||||
"applicationUrl": "https://localhost:7209;http://localhost:5063",
|
"applicationUrl": "https://localhost:7209;http://localhost:5063",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
|
"VbytesRelay__BaseUrl": "https://api.lan.vbytes.se",
|
||||||
|
"VbytesRelay__ApiKeyHeaderName": "X-Api-Key"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>92929106-8d00-401d-8f20-f372f475ea39</UserSecretsId>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,47 @@
|
||||||
@RegistrationAPI_HostAddress = http://localhost:5063
|
@RegistrationAPI_HostAddress = http://localhost:5063
|
||||||
|
|
||||||
|
### Clear all registrations
|
||||||
|
DELETE {{RegistrationAPI_HostAddress}}/api/Registration/clear
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
POST {{RegistrationAPI_HostAddress}}/api/participant/register
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"isMember": true,
|
||||||
|
"firstName": "Test",
|
||||||
|
"surName": "User",
|
||||||
|
"grade": "9",
|
||||||
|
"phoneNumber": "0700000000",
|
||||||
|
"email": "test.user@example.com",
|
||||||
|
"guardianName": "Guardian User",
|
||||||
|
"guardianPhoneNumber": "0700000001",
|
||||||
|
"guardianEmail": "guardian.user@example.com",
|
||||||
|
"isVisitor": false,
|
||||||
|
"hasApprovedGdpr": true,
|
||||||
|
"friends": "Friend One",
|
||||||
|
"specialDiet": "None"
|
||||||
|
}
|
||||||
|
|
||||||
|
### Register volunteer
|
||||||
|
POST {{RegistrationAPI_HostAddress}}/api/volunteer/register
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"firstName": "Jane",
|
||||||
|
"surName": "Doe",
|
||||||
|
"phoneNumber": "0700123456",
|
||||||
|
"email": "email@email.com",
|
||||||
|
"hasApprovedGdpr": true,
|
||||||
|
"areasOfInterest": [
|
||||||
|
{
|
||||||
|
"name": "Städ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Kiosk"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
22
src/Registration/Registration.API/Services/AuthService.cs
Normal file
22
src/Registration/Registration.API/Services/AuthService.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Registration.API.Configuration;
|
||||||
|
|
||||||
|
namespace Registration.API.Services;
|
||||||
|
|
||||||
|
public class AuthService(IHttpClientFactory httpClientFactory, IOptions<AuthApiOptions> options) : IAuthService
|
||||||
|
{
|
||||||
|
public const string HttpClientName = "AuthApi";
|
||||||
|
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
|
||||||
|
private readonly AuthApiOptions _options = options.Value;
|
||||||
|
|
||||||
|
public async Task<bool> IsMemberAsync(string ssn, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var client = _httpClientFactory.CreateClient(HttpClientName);
|
||||||
|
var response = await client.PostAsJsonAsync(
|
||||||
|
_options.ValidatePath,
|
||||||
|
new { ssn },
|
||||||
|
cancellationToken);
|
||||||
|
return response.IsSuccessStatusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Registration.API.Services;
|
||||||
|
|
||||||
|
public interface IAuthService
|
||||||
|
{
|
||||||
|
Task<bool> IsMemberAsync(string ssn, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
using Registration.Domain.Models;
|
||||||
|
|
||||||
|
namespace Registration.API.Services;
|
||||||
|
|
||||||
|
public interface IVbytesParticipantRelayService
|
||||||
|
{
|
||||||
|
Task<VbytesRelayResult> RegisterParticipantAsync(Participant participant, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record VbytesRelayResult(bool Success, int StatusCode, string Message);
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
using Registration.Domain.Models;
|
||||||
|
|
||||||
|
namespace Registration.API.Services;
|
||||||
|
|
||||||
|
public interface IVbytesVolunteerRelayService
|
||||||
|
{
|
||||||
|
Task<VbytesRelayResult> RegisterVolunteerAsync(Volunteer volunteer, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Registration.API.Configuration;
|
||||||
|
using Registration.API.DtoModels;
|
||||||
|
using Registration.Domain.Models;
|
||||||
|
|
||||||
|
namespace Registration.API.Services;
|
||||||
|
|
||||||
|
public class VbytesParticipantRelayService(
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
IOptions<VbytesRelayOptions> options,
|
||||||
|
ILogger<VbytesParticipantRelayService> logger)
|
||||||
|
: VbytesRelayServiceBase(httpClientFactory, options), IVbytesParticipantRelayService
|
||||||
|
{
|
||||||
|
public const string HttpClientName = "VbytesRelay";
|
||||||
|
|
||||||
|
private readonly ILogger<VbytesParticipantRelayService> _logger = logger;
|
||||||
|
|
||||||
|
public async Task<VbytesRelayResult> RegisterParticipantAsync(Participant participant, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var configError = ValidateConfiguration();
|
||||||
|
if (configError is not null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Relay configuration invalid: {Message}", configError.Message);
|
||||||
|
return configError;
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = Map(participant);
|
||||||
|
_logger.LogInformation("Relaying participant to {Path}. Payload: {@Payload}", Options.ParticipantRegisterPath, payload);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var result = await SendAsync(Options.ParticipantRegisterPath, payload, cancellationToken);
|
||||||
|
_logger.LogInformation("Relay response: {StatusCode} - {Message}", result.StatusCode, result.Message);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Relay timed out calling {Path}", Options.ParticipantRegisterPath);
|
||||||
|
return new VbytesRelayResult(false, StatusCodes.Status504GatewayTimeout, "Upstream timeout.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "VBytes participant relay exception.");
|
||||||
|
return new VbytesRelayResult(false, StatusCodes.Status502BadGateway, "Upstream relay failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ParticipantRelayDto Map(Participant participant) => new(
|
||||||
|
Member: participant.IsMember,
|
||||||
|
FirstName: participant.FirstName,
|
||||||
|
Surname: participant.SurName,
|
||||||
|
Grade: participant.Grade,
|
||||||
|
Phone: participant.PhoneNumber,
|
||||||
|
Email: participant.Email,
|
||||||
|
GuardianName: participant.GuardianName,
|
||||||
|
GuardianPhone: participant.GuardianPhoneNumber,
|
||||||
|
GuardianEmail: participant.GuardianEmail,
|
||||||
|
IsVisiting: participant.IsVisitor,
|
||||||
|
Gdpr: participant.HasApprovedGdpr,
|
||||||
|
Friends: participant.Friends,
|
||||||
|
SpecialDiet: participant.SpecialDiet
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Registration.API.Configuration;
|
||||||
|
|
||||||
|
namespace Registration.API.Services;
|
||||||
|
|
||||||
|
public abstract class VbytesRelayServiceBase(
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
IOptions<VbytesRelayOptions> options)
|
||||||
|
{
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
|
||||||
|
protected readonly VbytesRelayOptions Options = options.Value;
|
||||||
|
|
||||||
|
protected async Task<VbytesRelayResult> SendAsync(string path, object payload, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var client = _httpClientFactory.CreateClient(VbytesParticipantRelayService.HttpClientName);
|
||||||
|
|
||||||
|
using var request = new HttpRequestMessage(HttpMethod.Post, BuildPath(path))
|
||||||
|
{
|
||||||
|
Content = JsonContent.Create(payload)
|
||||||
|
};
|
||||||
|
|
||||||
|
request.Headers.TryAddWithoutValidation(Options.ApiKeyHeaderName, Options.ApiKey);
|
||||||
|
|
||||||
|
using var response = await client.SendAsync(request, cancellationToken);
|
||||||
|
var body = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[DEBUG] Relay success body: {body}");
|
||||||
|
return new VbytesRelayResult(true, StatusCodes.Status200OK, "Request relayed successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusCode = (int)response.StatusCode;
|
||||||
|
Console.WriteLine($"[DEBUG] Relay error body: {body}");
|
||||||
|
var message = body.Length > 600 ? body[..600] : body;
|
||||||
|
return new VbytesRelayResult(false, statusCode, string.IsNullOrWhiteSpace(message) ? "Upstream request failed." : message);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected VbytesRelayResult? ValidateConfiguration()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(Options.BaseUrl))
|
||||||
|
{
|
||||||
|
return new VbytesRelayResult(false, StatusCodes.Status500InternalServerError, "Relay base URL is not configured.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsConfigured(Options.ApiKey))
|
||||||
|
{
|
||||||
|
return new VbytesRelayResult(false, StatusCodes.Status500InternalServerError, "Relay API key is not configured.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildPath(string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(path))
|
||||||
|
{
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.StartsWith('/') ? path : $"/{path}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> ReadSafeErrorMessage(HttpResponseMessage response, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var body = await response.Content.ReadAsStringAsync(cancellationToken);
|
||||||
|
if (string.IsNullOrWhiteSpace(body))
|
||||||
|
{
|
||||||
|
return "Upstream request failed.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return body.Length > 600 ? body[..600] : body;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static bool IsConfigured(string value)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrWhiteSpace(value) && !value.StartsWith("__SET_", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Registration.API.Configuration;
|
||||||
|
using Registration.API.DtoModels;
|
||||||
|
using Registration.Domain.Models;
|
||||||
|
|
||||||
|
namespace Registration.API.Services;
|
||||||
|
|
||||||
|
public class VbytesVolunteerRelayService(
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
IOptions<VbytesRelayOptions> options,
|
||||||
|
ILogger<VbytesVolunteerRelayService> logger)
|
||||||
|
: VbytesRelayServiceBase(httpClientFactory, options), IVbytesVolunteerRelayService
|
||||||
|
{
|
||||||
|
private readonly ILogger<VbytesVolunteerRelayService> _logger = logger;
|
||||||
|
|
||||||
|
public async Task<VbytesRelayResult> RegisterVolunteerAsync(Volunteer volunteer, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var configError = ValidateConfiguration();
|
||||||
|
if (configError is not null) return configError;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await SendAsync(Options.VolunteerRegisterPath, Map(volunteer), cancellationToken);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
return new VbytesRelayResult(false, StatusCodes.Status504GatewayTimeout, "Upstream timeout.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "VBytes volunteer relay exception.");
|
||||||
|
return new VbytesRelayResult(false, StatusCodes.Status502BadGateway, "Upstream relay failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VolunteerRelayDto Map(Volunteer volunteer) => new(
|
||||||
|
FirstName: volunteer.FirstName,
|
||||||
|
Surname: volunteer.SurName,
|
||||||
|
Phone: volunteer.PhoneNumber,
|
||||||
|
Email: volunteer.Email,
|
||||||
|
Gdpr: volunteer.HasApprovedGdpr,
|
||||||
|
Areas: volunteer.AreasOfInterest.Select(area => area.Name).ToArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,18 @@
|
||||||
"DefaultConnection": "Host=localhost;Username=postgres;Password=postgres;Database=postgres;Port=5432"
|
"DefaultConnection": "Host=localhost;Username=postgres;Password=postgres;Database=postgres;Port=5432"
|
||||||
},
|
},
|
||||||
"Security": {
|
"Security": {
|
||||||
"SsnPepper": "VBYTES_LAN_2026_SECRET_PEPPER"
|
"SsnPepper": "__SET_IN_USER_SECRETS__"
|
||||||
|
},
|
||||||
|
"VbytesRelay": {
|
||||||
|
"BaseUrl": "https://api.lan.vbytes.se",
|
||||||
|
"ParticipantRegisterPath": "/api/participant",
|
||||||
|
"VolunteerRegisterPath": "/api/volunteer/register",
|
||||||
|
"ApiKeyHeaderName": "X-Api-Key",
|
||||||
|
"ApiKey": "",
|
||||||
|
"ClientCertificatePfxPath": ""
|
||||||
|
},
|
||||||
|
"AuthApi": {
|
||||||
|
"BaseUrl": "http://localhost:5127",
|
||||||
|
"ValidatePath": "/validate"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
30
src/Registration/Registration.API/appsettings.example.json
Normal file
30
src/Registration/Registration.API/appsettings.example.json
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": ""
|
||||||
|
},
|
||||||
|
"Security": {
|
||||||
|
"SsnPepper": ""
|
||||||
|
},
|
||||||
|
"Admin": {
|
||||||
|
"Password": ""
|
||||||
|
},
|
||||||
|
"VbytesRelay": {
|
||||||
|
"BaseUrl": "",
|
||||||
|
"ParticipantRegisterPath": "/api/participant",
|
||||||
|
"VolunteerRegisterPath": "",
|
||||||
|
"ApiKeyHeaderName": "",
|
||||||
|
"ApiKey": "",
|
||||||
|
"ClientCertificatePfxPath": ""
|
||||||
|
},
|
||||||
|
"AuthApi": {
|
||||||
|
"BaseUrl": "",
|
||||||
|
"ValidatePath": "/validate"
|
||||||
|
},
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "",
|
||||||
|
"Microsoft.AspNetCore": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": ""
|
||||||
|
}
|
||||||
|
|
@ -3,11 +3,23 @@
|
||||||
"DefaultConnection": "Host=localhost;Username=postgres;Password=postgres;Database=postgres;Port=5432"
|
"DefaultConnection": "Host=localhost;Username=postgres;Password=postgres;Database=postgres;Port=5432"
|
||||||
},
|
},
|
||||||
"Security": {
|
"Security": {
|
||||||
"SsnPepper": "VBYTES_LAN_2026_SECRET_PEPPER"
|
"SsnPepper": "__SET_IN_USER_SECRETS__"
|
||||||
},
|
},
|
||||||
"Admin": {
|
"Admin": {
|
||||||
"Password": "admin"
|
"Password": "admin"
|
||||||
},
|
},
|
||||||
|
"VbytesRelay": {
|
||||||
|
"BaseUrl": "https://api.lan.vbytes.se",
|
||||||
|
"ParticipantRegisterPath": "/api/participant",
|
||||||
|
"VolunteerRegisterPath": "/api/volunteer/register",
|
||||||
|
"ApiKeyHeaderName": "X-Api-Key",
|
||||||
|
"ApiKey": "__SET_IN_USER_SECRETS__",
|
||||||
|
"ClientCertificatePfxPath": "__SET_IN_USER_SECRETS__"
|
||||||
|
},
|
||||||
|
"AuthApi": {
|
||||||
|
"BaseUrl": "",
|
||||||
|
"ValidatePath": "/validate"
|
||||||
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,5 @@ public class Volunteer
|
||||||
public required string PhoneNumber { get; set; }
|
public required string PhoneNumber { get; set; }
|
||||||
public required string Email { get; set; }
|
public required string Email { get; set; }
|
||||||
public required bool HasApprovedGdpr { get; set; }
|
public required bool HasApprovedGdpr { get; set; }
|
||||||
public required AreasOfInterest AreasOfInterest { get; set; }
|
public required List<AreasOfInterest> AreasOfInterest { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue