Compare commits
No commits in common. "9d9c9b3f387dda08d0e092022fe66bf9228493b9" and "7c0030b08875012fd68ca8cfe17c7bc94a5ad1a3" have entirely different histories.
9d9c9b3f38
...
7c0030b088
31 changed files with 57 additions and 698 deletions
107
.vscode/tasks.json
vendored
107
.vscode/tasks.json
vendored
|
|
@ -1,107 +0,0 @@
|
||||||
{
|
|
||||||
"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,92 +23,3 @@ 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,10 +1,9 @@
|
||||||
<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,22 +1,20 @@
|
||||||
@AuthAPI_HostAddress = http://localhost:5127
|
@AuthAPI_HostAddress = http://localhost:5127
|
||||||
|
|
||||||
# Validate by SSN
|
GET {{AuthAPI_HostAddress}}/validate/
|
||||||
POST {{AuthAPI_HostAddress}}/validate
|
Accept: application/json
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"ssn": "8612125643"
|
"Email": "someValue",
|
||||||
|
"FirstName": "name"
|
||||||
}
|
}
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
# Validate by email + first name
|
GET {{AuthAPI_HostAddress}}/validate/
|
||||||
POST {{AuthAPI_HostAddress}}/validate
|
Accept: application/json
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"email": "testwith@validemail.se",
|
"Ssn": "10 or 8 number length"
|
||||||
"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("EnvironmentVariables"));
|
builder.Configuration.GetSection("EnviromentVariables"));
|
||||||
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.MapPost("/validate", async (
|
app.MapGet("/validate", async (
|
||||||
[FromBody] Request validationRequest,
|
[FromBody] Request validationRequest,
|
||||||
HttpClient httpClient,
|
HttpClient httpClient,
|
||||||
MemberValidationService memberService,
|
MemberValidationService memberService,
|
||||||
|
|
@ -32,8 +32,7 @@ app.MapPost("/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();
|
||||||
Console.WriteLine($"[DEBUG] Sverok response: {content}");
|
return content.Contains("\"member_found\":true,") ? Results.Ok() : Results.NotFound();
|
||||||
return content.Contains("\"member_found\":true") ? Results.Ok() : Results.NotFound();
|
|
||||||
})
|
})
|
||||||
.WithName("ValidateMember");
|
.WithName("ValidateMember");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"EnvironmentVariables": {
|
"EnviromentVariables": {
|
||||||
"ApiUrl": "",
|
"ApiUrl": "",
|
||||||
"ApiKey": "",
|
"ApiKey": "",
|
||||||
"AssociationNumber": ""
|
"AssociationNumber": ""
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"EnvironmentVariables": {
|
|
||||||
"ApiUrl": "",
|
|
||||||
"ApiKey": "",
|
|
||||||
"AssociationNumber": ""
|
|
||||||
},
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "",
|
|
||||||
"Microsoft.AspNetCore": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": ""
|
|
||||||
}
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"EnvironmentVariables": {
|
"EnviromentVariables": {
|
||||||
"ApiUrl": "",
|
"ApiUrl": "",
|
||||||
"ApiKey": "",
|
"ApiKey": "",
|
||||||
"AssociationNumber": ""
|
"AssociationNumber": ""
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Registration.API.Configuration;
|
|
||||||
|
|
||||||
public class AuthApiOptions
|
|
||||||
{
|
|
||||||
public string BaseUrl { get; set; } = string.Empty;
|
|
||||||
public string ValidatePath { get; set; } = "/validate";
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
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,26 +1,16 @@
|
||||||
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(IVbytesParticipantRelayService relayService) : ControllerBase
|
public class ParticipantController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IVbytesParticipantRelayService _relayService = relayService;
|
|
||||||
|
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
public async Task<IActionResult> RegisterForLan([FromBody] Participant participant, CancellationToken cancellationToken)
|
public IActionResult RegisterForLan([FromBody] Participant participant)
|
||||||
{
|
{
|
||||||
var result = await _relayService.RegisterParticipantAsync(participant, cancellationToken);
|
return Ok();
|
||||||
|
|
||||||
if (result.Success)
|
|
||||||
{
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
return StatusCode(result.StatusCode, new { message = result.Message });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,26 @@
|
||||||
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, IAuthService authService) : ControllerBase
|
public class RegistrationController(IMemberRepository memberRepository) : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IMemberRepository _memberRepository = memberRepository;
|
private readonly IMemberRepository _memberRepository = memberRepository;
|
||||||
private readonly IAuthService _authService = authService;
|
|
||||||
|
|
||||||
[HttpGet("register/{ssn}")]
|
[HttpGet("register/{ssn}")]
|
||||||
public async Task<IActionResult> ValidateSsn(string ssn, CancellationToken cancellationToken)
|
public IActionResult ValidateSsn(string ssn)
|
||||||
{
|
{
|
||||||
var isMember = await _authService.IsMemberAsync(ssn, cancellationToken);
|
// Should talk to the auth api to validate the ssn properly.
|
||||||
return isMember ? Ok() : NotFound();
|
if (ssn.Length == 10 && long.TryParse(ssn, out _))
|
||||||
|
{
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("register/{ssn}")]
|
[HttpPost("register/{ssn}")]
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,16 @@
|
||||||
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(IVbytesVolunteerRelayService relayService) : ControllerBase
|
public class VolunteerController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly IVbytesVolunteerRelayService _relayService = relayService;
|
|
||||||
|
|
||||||
[HttpPost("register")]
|
[HttpPost("register")]
|
||||||
public async Task<IActionResult> RegisterVolunteer([FromBody] Volunteer volunteer, CancellationToken cancellationToken)
|
public IActionResult RegisterVolunteer([FromBody] Volunteer volunteer)
|
||||||
{
|
{
|
||||||
var result = await _relayService.RegisterVolunteerAsync(volunteer, cancellationToken);
|
return Ok();
|
||||||
|
|
||||||
if (result.Success)
|
|
||||||
{
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
return StatusCode(result.StatusCode, new { message = result.Message });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
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,49 +1,10 @@
|
||||||
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();
|
||||||
|
|
@ -61,11 +22,7 @@ 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,9 +7,7 @@
|
||||||
"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": {
|
||||||
|
|
@ -18,9 +16,7 @@
|
||||||
"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,10 +1,9 @@
|
||||||
<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,47 +1,6 @@
|
||||||
@RegistrationAPI_HostAddress = http://localhost:5063
|
@RegistrationAPI_HostAddress = http://localhost:5063
|
||||||
|
|
||||||
### Clear all registrations
|
GET {{RegistrationAPI_HostAddress}}/weatherforecast/
|
||||||
DELETE {{RegistrationAPI_HostAddress}}/api/Registration/clear
|
Accept: application/json
|
||||||
|
|
||||||
###
|
###
|
||||||
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace Registration.API.Services;
|
|
||||||
|
|
||||||
public interface IAuthService
|
|
||||||
{
|
|
||||||
Task<bool> IsMemberAsync(string ssn, CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
using Registration.Domain.Models;
|
|
||||||
|
|
||||||
namespace Registration.API.Services;
|
|
||||||
|
|
||||||
public interface IVbytesVolunteerRelayService
|
|
||||||
{
|
|
||||||
Task<VbytesRelayResult> RegisterVolunteerAsync(Volunteer volunteer, CancellationToken cancellationToken = default);
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
13
src/Registration/Registration.API/WeatherForecast.cs
Normal file
13
src/Registration/Registration.API/WeatherForecast.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace RegistrationAPI;
|
||||||
|
|
||||||
|
public class WeatherForecast
|
||||||
|
{
|
||||||
|
public DateOnly Date { get; set; }
|
||||||
|
|
||||||
|
public int TemperatureC { get; set; }
|
||||||
|
|
||||||
|
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||||
|
|
||||||
|
public string? Summary { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -9,18 +9,6 @@
|
||||||
"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": "__SET_IN_USER_SECRETS__"
|
"SsnPepper": "VBYTES_LAN_2026_SECRET_PEPPER"
|
||||||
},
|
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
{
|
|
||||||
"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,23 +3,11 @@
|
||||||
"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": "__SET_IN_USER_SECRETS__"
|
"SsnPepper": "VBYTES_LAN_2026_SECRET_PEPPER"
|
||||||
},
|
},
|
||||||
"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 List<AreasOfInterest> AreasOfInterest { get; set; }
|
public required AreasOfInterest AreasOfInterest { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue