From e579d7656015a407239e62465a50946d2e7d961c Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 17 Aug 2024 16:38:40 +0200 Subject: [PATCH 001/150] Create api project --- .dockerignore | 30 ++ .gitignore | 484 +++++++++++++++++++++++++ src/Api/Api.csproj | 19 + src/Api/Dockerfile | 25 ++ src/Api/Program.cs | 6 + src/Api/Properties/launchSettings.json | 52 +++ src/Api/StartupExtensions.cs | 29 ++ src/Api/appsettings.Development.json | 8 + src/Api/appsettings.json | 9 + vegasco-server.sln | 22 ++ 10 files changed, 684 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 src/Api/Api.csproj create mode 100644 src/Api/Dockerfile create mode 100644 src/Api/Program.cs create mode 100644 src/Api/Properties/launchSettings.json create mode 100644 src/Api/StartupExtensions.cs create mode 100644 src/Api/appsettings.Development.json create mode 100644 src/Api/appsettings.json create mode 100644 vegasco-server.sln diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..104b544 --- /dev/null +++ b/.gitignore @@ -0,0 +1,484 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/src/Api/Api.csproj b/src/Api/Api.csproj new file mode 100644 index 0000000..346136f --- /dev/null +++ b/src/Api/Api.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + 4bf893d3-0c16-41ec-8b46-2768d841215d + Linux + ..\.. + Vegasco.Api + + + + + + + + + diff --git a/src/Api/Dockerfile b/src/Api/Dockerfile new file mode 100644 index 0000000..25719d5 --- /dev/null +++ b/src/Api/Dockerfile @@ -0,0 +1,25 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER app +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["src/Api/Api.csproj", "src/Api/"] +RUN dotnet restore "./src/Api/Api.csproj" +COPY . . +WORKDIR "/src/src/Api" +RUN dotnet build "./Api.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Api.dll"] \ No newline at end of file diff --git a/src/Api/Program.cs b/src/Api/Program.cs new file mode 100644 index 0000000..b072d92 --- /dev/null +++ b/src/Api/Program.cs @@ -0,0 +1,6 @@ +using Vegasco.Api; + +WebApplication.CreateBuilder(args) + .ConfigureServices() + .ConfigureRequestPipeline() + .Run(); diff --git a/src/Api/Properties/launchSettings.json b/src/Api/Properties/launchSettings.json new file mode 100644 index 0000000..a2d8742 --- /dev/null +++ b/src/Api/Properties/launchSettings.json @@ -0,0 +1,52 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5076" + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7098;http://localhost:5076" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Container (Dockerfile)": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", + "environmentVariables": { + "ASPNETCORE_HTTPS_PORTS": "8081", + "ASPNETCORE_HTTP_PORTS": "8080" + }, + "publishAllPorts": true, + "useSSL": true + } + }, + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:22111", + "sslPort": 44373 + } + } +} \ No newline at end of file diff --git a/src/Api/StartupExtensions.cs b/src/Api/StartupExtensions.cs new file mode 100644 index 0000000..a785df1 --- /dev/null +++ b/src/Api/StartupExtensions.cs @@ -0,0 +1,29 @@ +namespace Vegasco.Api; + +internal static class StartupExtensions +{ + internal static WebApplication ConfigureServices(this WebApplicationBuilder builder) + { + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + + builder.Services.AddHealthChecks(); + + return builder.Build(); + } + + internal static WebApplication ConfigureRequestPipeline(this WebApplication app) + { + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + app.UseHttpsRedirection(); + + app.MapHealthChecks("/health"); + + return app; + } +} \ No newline at end of file diff --git a/src/Api/appsettings.Development.json b/src/Api/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/Api/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Api/appsettings.json b/src/Api/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/src/Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/vegasco-server.sln b/vegasco-server.sln new file mode 100644 index 0000000..366336c --- /dev/null +++ b/vegasco-server.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api", "src\Api\Api.csproj", "{9FF3C98A-5085-4EBE-A980-DB2148B0C00A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal -- 2.49.1 From a708ed25e7cf5491bc0193b551e0161d41efc4bd Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 17 Aug 2024 16:38:40 +0200 Subject: [PATCH 002/150] Initial endpoint configuration with authentication --- README.md | 59 ++++++++- src/Api/Api.csproj | 19 --- src/Api/Program.cs | 6 - src/Api/StartupExtensions.cs | 29 ---- src/WebApi/Authentication/JwtOptions.cs | 31 +++++ src/WebApi/Cars/Car.cs | 10 ++ src/WebApi/Cars/CreateCar.cs | 38 ++++++ src/WebApi/Common/Constants.cs | 11 ++ .../Common/DependencyInjectionExtensions.cs | 124 ++++++++++++++++++ src/WebApi/Common/IWebApiMarker.cs | 3 + src/WebApi/Common/StartupExtensions.cs | 62 +++++++++ src/WebApi/Common/ValidatorExtensions.cs | 94 +++++++++++++ src/{Api => WebApi}/Dockerfile | 0 src/WebApi/Endpoints/EndpointExtensions.cs | 39 ++++++ src/WebApi/Endpoints/IEndpoint.cs | 6 + .../OpenApi/ConfigureSwaggerGenOptions.cs | 56 ++++++++ .../Endpoints/OpenApi/SwaggerDocConstants.cs | 6 + src/WebApi/Program.cs | 6 + .../Properties/launchSettings.json | 0 src/WebApi/WebApi.csproj | 28 ++++ .../appsettings.Development.json | 0 src/{Api => WebApi}/appsettings.json | 0 vegasco-server.sln | 17 ++- 23 files changed, 587 insertions(+), 57 deletions(-) delete mode 100644 src/Api/Api.csproj delete mode 100644 src/Api/Program.cs delete mode 100644 src/Api/StartupExtensions.cs create mode 100644 src/WebApi/Authentication/JwtOptions.cs create mode 100644 src/WebApi/Cars/Car.cs create mode 100644 src/WebApi/Cars/CreateCar.cs create mode 100644 src/WebApi/Common/Constants.cs create mode 100644 src/WebApi/Common/DependencyInjectionExtensions.cs create mode 100644 src/WebApi/Common/IWebApiMarker.cs create mode 100644 src/WebApi/Common/StartupExtensions.cs create mode 100644 src/WebApi/Common/ValidatorExtensions.cs rename src/{Api => WebApi}/Dockerfile (100%) create mode 100644 src/WebApi/Endpoints/EndpointExtensions.cs create mode 100644 src/WebApi/Endpoints/IEndpoint.cs create mode 100644 src/WebApi/Endpoints/OpenApi/ConfigureSwaggerGenOptions.cs create mode 100644 src/WebApi/Endpoints/OpenApi/SwaggerDocConstants.cs create mode 100644 src/WebApi/Program.cs rename src/{Api => WebApi}/Properties/launchSettings.json (100%) create mode 100644 src/WebApi/WebApi.csproj rename src/{Api => WebApi}/appsettings.Development.json (100%) rename src/{Api => WebApi}/appsettings.json (100%) diff --git a/README.md b/README.md index 0845b31..8adf979 100644 --- a/README.md +++ b/README.md @@ -1 +1,58 @@ -# vegasco-server \ No newline at end of file +# Vegasco Server + +Backend for the vegasco (***VE***hicle ***GAS*** ***CO***nsumption) application. + +## Getting Started + +### Configuration + +| Configuration | Description | Default | Required | +| --- | --- | --- | --- | +| JWT:Authority | The authority of the JWT token. | - | true | +| JWT:Audience | The audience of the JWT token. | - | true | +| JWT:Issuer | The issuer of the JWT token. | - | true | +| JWT:NameClaimType | The type of the name claim. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name` (C# constant `ClaimTypes.Name` | false | + +The application uses the prefix `Vegasco_` for environment variable names. The prefix is removed when the application reads the environment variables and duplicate entries are overwritten by the environment variables. + +Example: + +- `foo=bar1` +- `Vegasco_foo=bar2` + +Results in: + +- `foo=bar2` +- `Vegasco_foo=bar2` + +Configuration hierarchy in environment variables is usually denoted using a colon (`:`). But because on some systems the colon character is a reserved character, you can use a double underscore (`__`) as an alternative. The application will replace the double underscore with a colon when reading the environment variables. + +Example: + +The environment variable `foo__bar=value` (as well as `Vegasco_foo__bar=value`) will be converted to `foo:bar=value` in the application. + +### Configuration examples + +As environment variables: + +```env +Vegasco_JWT__Authority=https://example.authority.com +Vegasco_JWT__Audience=example-audience +Vegasco_JWT__Issuer=https://example.authority.com/realms/example-realm/ +Vegasco_JWT__NameClaimType=preferred_username +``` + +As appsettings.json (or a environment specific appsettings.*.json): + +**Note: the `Vegasco_` prefix is only for environment variables** + +```json +{ + "JWT": { + "Authority": "https://example.authority.com/realms/example-realm", + "Audience": "example-audience", + "Issuer": "https://example.authority.com/realms/example-realm/", + "NameClaimType": "preferred_username" + } +} +``` diff --git a/src/Api/Api.csproj b/src/Api/Api.csproj deleted file mode 100644 index 346136f..0000000 --- a/src/Api/Api.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net8.0 - enable - enable - 4bf893d3-0c16-41ec-8b46-2768d841215d - Linux - ..\.. - Vegasco.Api - - - - - - - - - diff --git a/src/Api/Program.cs b/src/Api/Program.cs deleted file mode 100644 index b072d92..0000000 --- a/src/Api/Program.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Vegasco.Api; - -WebApplication.CreateBuilder(args) - .ConfigureServices() - .ConfigureRequestPipeline() - .Run(); diff --git a/src/Api/StartupExtensions.cs b/src/Api/StartupExtensions.cs deleted file mode 100644 index a785df1..0000000 --- a/src/Api/StartupExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Vegasco.Api; - -internal static class StartupExtensions -{ - internal static WebApplication ConfigureServices(this WebApplicationBuilder builder) - { - builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(); - - builder.Services.AddHealthChecks(); - - return builder.Build(); - } - - internal static WebApplication ConfigureRequestPipeline(this WebApplication app) - { - if (app.Environment.IsDevelopment()) - { - app.UseSwagger(); - app.UseSwaggerUI(); - } - - app.UseHttpsRedirection(); - - app.MapHealthChecks("/health"); - - return app; - } -} \ No newline at end of file diff --git a/src/WebApi/Authentication/JwtOptions.cs b/src/WebApi/Authentication/JwtOptions.cs new file mode 100644 index 0000000..e05daa0 --- /dev/null +++ b/src/WebApi/Authentication/JwtOptions.cs @@ -0,0 +1,31 @@ +using FluentValidation; + +namespace Vegasco.WebApi.Authentication; + +public class JwtOptions +{ + public const string SectionName = "JWT"; + + public string Audience { get; set; } = ""; + + public string Authority { get; set; } = ""; + + public string Issuer { get; set; } = ""; + + public string? NameClaimType { get; set; } +} + +public class JwtOptionsValidator : AbstractValidator +{ + public JwtOptionsValidator() + { + RuleFor(x => x.Audience) + .NotEmpty(); + + RuleFor(x => x.Authority) + .NotEmpty(); + + RuleFor(x => x.Issuer) + .NotEmpty(); + } +} \ No newline at end of file diff --git a/src/WebApi/Cars/Car.cs b/src/WebApi/Cars/Car.cs new file mode 100644 index 0000000..da1e72b --- /dev/null +++ b/src/WebApi/Cars/Car.cs @@ -0,0 +1,10 @@ +namespace Vegasco.WebApi.Cars; + +public class Car +{ + public Guid Id { get; set; } = Guid.NewGuid(); + + public string Name { get; set; } = ""; + + public Guid UserId { get; set; } +} diff --git a/src/WebApi/Cars/CreateCar.cs b/src/WebApi/Cars/CreateCar.cs new file mode 100644 index 0000000..ed7630e --- /dev/null +++ b/src/WebApi/Cars/CreateCar.cs @@ -0,0 +1,38 @@ +using FluentValidation; +using FluentValidation.Results; +using Vegasco.WebApi.Common; + +namespace Vegasco.WebApi.Cars; + +public static class CreateCar +{ + public record Request(string Name); + public record Response(Guid Id, string Name); + + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapPost("cars", Handler) + .WithTags("Cars"); + } + + public class Validator : AbstractValidator + { + public Validator() + { + RuleFor(x => x.Name) + .NotEmpty(); + } + } + + public static async Task Handler(Request request, IEnumerable> validators) + { + List failedValidations = await validators.ValidateAllAsync(request); + if (failedValidations.Count > 0) + { + return Results.BadRequest(new HttpValidationProblemDetails(failedValidations.ToCombinedDictionary())); + } + + return Results.Ok(); + } +} diff --git a/src/WebApi/Common/Constants.cs b/src/WebApi/Common/Constants.cs new file mode 100644 index 0000000..bc15221 --- /dev/null +++ b/src/WebApi/Common/Constants.cs @@ -0,0 +1,11 @@ +namespace Vegasco.WebApi.Common; + +public static class Constants +{ + public const string AppOtelName = "Vegasco.Api"; + + public static class Authorization + { + public const string RequireAuthenticatedUserPolicy = "RequireAuthenticatedUser"; + } +} \ No newline at end of file diff --git a/src/WebApi/Common/DependencyInjectionExtensions.cs b/src/WebApi/Common/DependencyInjectionExtensions.cs new file mode 100644 index 0000000..19de60f --- /dev/null +++ b/src/WebApi/Common/DependencyInjectionExtensions.cs @@ -0,0 +1,124 @@ +using Asp.Versioning; +using FluentValidation; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.Options; +using OpenTelemetry.Trace; +using System.Diagnostics; +using Vegasco.WebApi.Authentication; +using Vegasco.WebApi.Endpoints; +using Vegasco.WebApi.Endpoints.OpenApi; + +namespace Vegasco.WebApi.Common; + +public static class DependencyInjectionExtensions +{ + /// + /// Adds all the WebApi related services to the Dependency Injection container. + /// + /// + public static void AddWebApiServices(this IServiceCollection services) + { + services + .AddMiscellaneousServices() + .AddOpenApi() + .AddApiVersioning() + .AddOtel() + .AddAuthenticationAndAuthorization(); + } + + private static IServiceCollection AddMiscellaneousServices(this IServiceCollection services) + { + services.AddResponseCompression(); + + services.AddValidatorsFromAssemblies( + [ + typeof(IWebApiMarker).Assembly + ], ServiceLifetime.Singleton); + + services.AddHealthChecks(); + services.AddEndpointsFromAssemblyContaining(); + + return services; + } + + private static IServiceCollection AddOpenApi(this IServiceCollection services) + { + services.ConfigureOptions(); + + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(); + + return services; + } + + private static IServiceCollection AddApiVersioning(this IServiceCollection services) + { + services.AddApiVersioning(o => + { + o.DefaultApiVersion = new ApiVersion(1); + o.ApiVersionReader = new UrlSegmentApiVersionReader(); + o.ReportApiVersions = true; + }) + .AddApiExplorer(o => + { + o.GroupNameFormat = "'v'V"; + o.SubstituteApiVersionInUrl = true; + }); + + return services; + } + + private static IServiceCollection AddOtel(this IServiceCollection services) + { + Activity.DefaultIdFormat = ActivityIdFormat.W3C; + + ActivitySource activitySource = new(Constants.AppOtelName); + services.AddSingleton(activitySource); + + services.AddOpenTelemetry() + .WithTracing(t => + { + t.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddOtlpExporter() + .AddSource(activitySource.Name); + }) + .WithMetrics(); + + return services; + } + + private static IServiceCollection AddAuthenticationAndAuthorization(this IServiceCollection services) + { + services.AddOptions() + .BindConfiguration(JwtOptions.SectionName) + .ValidateFluently() + .ValidateOnStart(); + + var jwtOptions = services.BuildServiceProvider().GetRequiredService>(); + + services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) + .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o => + { + o.Authority = jwtOptions.Value.Authority; + + o.TokenValidationParameters.ValidAudience = jwtOptions.Value.Audience; + o.TokenValidationParameters.ValidateAudience = true; + + o.TokenValidationParameters.ValidIssuer = jwtOptions.Value.Issuer; + o.TokenValidationParameters.ValidateIssuer = true; + + if (!string.IsNullOrWhiteSpace(jwtOptions.Value.NameClaimType)) + { + o.TokenValidationParameters.NameClaimType = jwtOptions.Value.NameClaimType; + } + }); + + services.AddAuthorizationBuilder() + .AddPolicy(Constants.Authorization.RequireAuthenticatedUserPolicy, p => p + .RequireAuthenticatedUser() + .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)); + + return services; + } +} diff --git a/src/WebApi/Common/IWebApiMarker.cs b/src/WebApi/Common/IWebApiMarker.cs new file mode 100644 index 0000000..bcd335d --- /dev/null +++ b/src/WebApi/Common/IWebApiMarker.cs @@ -0,0 +1,3 @@ +namespace Vegasco.WebApi.Common; + +public interface IWebApiMarker; diff --git a/src/WebApi/Common/StartupExtensions.cs b/src/WebApi/Common/StartupExtensions.cs new file mode 100644 index 0000000..2e1b332 --- /dev/null +++ b/src/WebApi/Common/StartupExtensions.cs @@ -0,0 +1,62 @@ +using Asp.Versioning.ApiExplorer; +using Microsoft.AspNetCore.Localization; +using System.Globalization; +using Vegasco.WebApi.Endpoints; + +namespace Vegasco.WebApi.Common; + +internal static class StartupExtensions +{ + internal static WebApplication ConfigureServices(this WebApplicationBuilder builder) + { + builder.Configuration.AddEnvironmentVariables("Vegasco_"); + + builder.Services.AddWebApiServices(); + + WebApplication app = builder.Build(); + return app; + } + + internal static WebApplication ConfigureRequestPipeline(this WebApplication app) + { + app.UseRequestLocalization(o => + { + o.SupportedCultures = + [ + new CultureInfo("en") + ]; + + o.SupportedUICultures = o.SupportedCultures; + + CultureInfo defaultCulture = o.SupportedCultures[0]; + o.DefaultRequestCulture = new RequestCulture(defaultCulture); + }); + + app.UseHttpsRedirection(); + + app.MapHealthChecks("/health"); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.MapEndpoints(); + + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(o => + { + // Create a Swagger endpoint for each API version + IReadOnlyList apiVersions = app.DescribeApiVersions(); + foreach (ApiVersionDescription apiVersionDescription in apiVersions) + { + string url = $"/swagger/{apiVersionDescription.GroupName}/swagger.json"; + string name = apiVersionDescription.GroupName.ToUpperInvariant(); + o.SwaggerEndpoint(url, name); + } + }); + } + + return app; + } +} diff --git a/src/WebApi/Common/ValidatorExtensions.cs b/src/WebApi/Common/ValidatorExtensions.cs new file mode 100644 index 0000000..2ee107f --- /dev/null +++ b/src/WebApi/Common/ValidatorExtensions.cs @@ -0,0 +1,94 @@ +using FluentValidation; +using FluentValidation.Results; +using Microsoft.Extensions.Options; + +namespace Vegasco.WebApi.Common; + +public static class ValidatorExtensions +{ + /// + /// Asynchronously validates an instance of against all instances in . + /// + /// + /// + /// + /// The failed validation results. + public static async Task> ValidateAllAsync(this IEnumerable> validators, T instance) + { + var validationTasks = validators + .Select(validator => validator.ValidateAsync(instance)) + .ToList(); + + await Task.WhenAll(validationTasks); + + List failedValidations = validationTasks + .Select(x => x.Result) + .Where(x => !x.IsValid) + .ToList(); + + return failedValidations; + } + + public static Dictionary ToCombinedDictionary(this IEnumerable validationResults) + { + // Use a hash set to avoid duplicate error messages. + Dictionary> combinedErrors = []; + + foreach (var error in validationResults.SelectMany(x => x.Errors)) + { + if (!combinedErrors.TryGetValue(error.PropertyName, out HashSet? value)) + { + value = ([error.ErrorMessage]); + combinedErrors[error.PropertyName] = value; + continue; + } + + value.Add(error.ErrorMessage); + } + + return combinedErrors.ToDictionary(x => x.Key, x => x.Value.ToArray()); + } + + public static OptionsBuilder ValidateFluently(this OptionsBuilder builder) + where T : class + { + builder.Services.AddTransient>(serviceProvider => + { + var validators = serviceProvider.GetServices>() ?? []; + return new FluentValidationOptions(builder.Name, validators); + }); + return builder; + } +} + +internal class FluentValidationOptions : IValidateOptions + where TOptions : class +{ + private readonly IEnumerable> _validators; + + public string? Name { get; set; } + + public FluentValidationOptions(string? name, IEnumerable> validators) + { + Name = name; + _validators = validators; + } + + public ValidateOptionsResult Validate(string? name, TOptions options) + { + if (name is not null && name != Name) + { + return ValidateOptionsResult.Skip; + } + + ArgumentNullException.ThrowIfNull(options); + + var failedValidations = _validators.ValidateAllAsync(options).Result; + if (failedValidations.Count == 0) + { + return ValidateOptionsResult.Success; + } + + return ValidateOptionsResult.Fail(failedValidations.SelectMany(x => x.Errors.Select(x => x.ErrorMessage))); + } +} \ No newline at end of file diff --git a/src/Api/Dockerfile b/src/WebApi/Dockerfile similarity index 100% rename from src/Api/Dockerfile rename to src/WebApi/Dockerfile diff --git a/src/WebApi/Endpoints/EndpointExtensions.cs b/src/WebApi/Endpoints/EndpointExtensions.cs new file mode 100644 index 0000000..17cc430 --- /dev/null +++ b/src/WebApi/Endpoints/EndpointExtensions.cs @@ -0,0 +1,39 @@ +using Asp.Versioning.Builder; +using Asp.Versioning.Conventions; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Vegasco.WebApi.Cars; +using Vegasco.WebApi.Common; + +namespace Vegasco.WebApi.Endpoints; + +public static class EndpointExtensions +{ + public static IServiceCollection AddEndpointsFromAssemblyContaining(this IServiceCollection services) + { + var assembly = typeof(T).Assembly; + + ServiceDescriptor[] serviceDescriptors = assembly + .DefinedTypes + .Where(type => type is { IsAbstract: false, IsInterface: false } && + type.IsAssignableTo(typeof(IEndpoint))) + .Select(type => ServiceDescriptor.Transient(typeof(IEndpoint), type)) + .ToArray(); + + services.TryAddEnumerable(serviceDescriptors); + + return services; + } + + public static void MapEndpoints(this IEndpointRouteBuilder builder) + { + ApiVersionSet apiVersionSet = builder.NewApiVersionSet() + .HasApiVersion(1.0) + .Build(); + + RouteGroupBuilder versionedApis = builder.MapGroup("/v{apiVersion:apiVersion}") + .WithApiVersionSet(apiVersionSet) + .RequireAuthorization(Constants.Authorization.RequireAuthenticatedUserPolicy); + + CreateCar.MapEndpoint(versionedApis); + } +} diff --git a/src/WebApi/Endpoints/IEndpoint.cs b/src/WebApi/Endpoints/IEndpoint.cs new file mode 100644 index 0000000..15e0c50 --- /dev/null +++ b/src/WebApi/Endpoints/IEndpoint.cs @@ -0,0 +1,6 @@ +namespace Vegasco.WebApi.Endpoints; + +public interface IEndpoint +{ + void MapEndpoint(IEndpointRouteBuilder builder); +} diff --git a/src/WebApi/Endpoints/OpenApi/ConfigureSwaggerGenOptions.cs b/src/WebApi/Endpoints/OpenApi/ConfigureSwaggerGenOptions.cs new file mode 100644 index 0000000..571dde8 --- /dev/null +++ b/src/WebApi/Endpoints/OpenApi/ConfigureSwaggerGenOptions.cs @@ -0,0 +1,56 @@ +using Asp.Versioning.ApiExplorer; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Vegasco.WebApi.Endpoints.OpenApi; + +/// +/// Registers each api version as its own swagger document. +/// +/// +public class ConfigureSwaggerGenOptions( + IApiVersionDescriptionProvider versionDescriptionProvider) + : IConfigureNamedOptions +{ + private readonly IApiVersionDescriptionProvider _versionDescriptionProvider = versionDescriptionProvider; + + public void Configure(SwaggerGenOptions options) + { + foreach (ApiVersionDescription description in _versionDescriptionProvider.ApiVersionDescriptions) + { + OpenApiSecurityScheme securityScheme = new() + { + Name = "Bearer", + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Scheme = "bearer", + Reference = new OpenApiReference + { + Id = IdentityConstants.BearerScheme, + Type = ReferenceType.SecurityScheme + } + }; + options.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme); + + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { securityScheme, Array.Empty() } + }); + + OpenApiInfo openApiInfo = new() + { + Title = "Vegasco API", + Version = description.ApiVersion.ToString() + }; + + options.SwaggerDoc(description.GroupName, openApiInfo); + } + } + + public void Configure(string? name, SwaggerGenOptions options) + { + Configure(options); + } +} diff --git a/src/WebApi/Endpoints/OpenApi/SwaggerDocConstants.cs b/src/WebApi/Endpoints/OpenApi/SwaggerDocConstants.cs new file mode 100644 index 0000000..e88de76 --- /dev/null +++ b/src/WebApi/Endpoints/OpenApi/SwaggerDocConstants.cs @@ -0,0 +1,6 @@ +namespace Vegasco.WebApi.Endpoints.OpenApi; + +public static class SwaggerDocConstants +{ + +} diff --git a/src/WebApi/Program.cs b/src/WebApi/Program.cs new file mode 100644 index 0000000..fd643eb --- /dev/null +++ b/src/WebApi/Program.cs @@ -0,0 +1,6 @@ +using Vegasco.WebApi.Common; + +WebApplication.CreateBuilder(args) + .ConfigureServices() + .ConfigureRequestPipeline() + .Run(); diff --git a/src/Api/Properties/launchSettings.json b/src/WebApi/Properties/launchSettings.json similarity index 100% rename from src/Api/Properties/launchSettings.json rename to src/WebApi/Properties/launchSettings.json diff --git a/src/WebApi/WebApi.csproj b/src/WebApi/WebApi.csproj new file mode 100644 index 0000000..aaa80ac --- /dev/null +++ b/src/WebApi/WebApi.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + 4bf893d3-0c16-41ec-8b46-2768d841215d + Linux + ..\.. + Vegasco.WebApi + + + + + + + + + + + + + + + + + + diff --git a/src/Api/appsettings.Development.json b/src/WebApi/appsettings.Development.json similarity index 100% rename from src/Api/appsettings.Development.json rename to src/WebApi/appsettings.Development.json diff --git a/src/Api/appsettings.json b/src/WebApi/appsettings.json similarity index 100% rename from src/Api/appsettings.json rename to src/WebApi/appsettings.json diff --git a/vegasco-server.sln b/vegasco-server.sln index 366336c..da38768 100644 --- a/vegasco-server.sln +++ b/vegasco-server.sln @@ -1,9 +1,16 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 +# 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api", "src\Api\Api.csproj", "{9FF3C98A-5085-4EBE-A980-DB2148B0C00A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "src\WebApi\WebApi.csproj", "{9FF3C98A-5085-4EBE-A980-DB2148B0C00A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C051A684-BD6A-43F2-B0CC-F3C2315D99E3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A16251C2-47DB-4017-812B-CA18B280E049}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -19,4 +26,10 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {9FF3C98A-5085-4EBE-A980-DB2148B0C00A} = {C051A684-BD6A-43F2-B0CC-F3C2315D99E3} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7813E32D-AE19-479C-853B-063882D2D05A} + EndGlobalSection EndGlobal -- 2.49.1 From 877e7989cd156525486df86e4ae99a3e8018faa1 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 17 Aug 2024 16:38:40 +0200 Subject: [PATCH 003/150] Implement all car endpoints --- Create-MigrationScript.ps1 | 1 + migrations/migration.sql | 48 ++++++++++++ src/WebApi/Authentication/UserAccessor.cs | 78 +++++++++++++++++++ src/WebApi/Cars/Car.cs | 30 ++++++- src/WebApi/Cars/CreateCar.cs | 43 ++++++++-- src/WebApi/Cars/DeleteCar.cs | 31 ++++++++ src/WebApi/Cars/GetCar.cs | 31 ++++++++ src/WebApi/Cars/GetCars.cs | 27 +++++++ src/WebApi/Cars/UpdateCar.cs | 58 ++++++++++++++ .../Common/DependencyInjectionExtensions.cs | 46 ++++++++++- src/WebApi/Common/FluentValidationOptions.cs | 36 +++++++++ src/WebApi/Common/StartupExtensions.cs | 2 +- src/WebApi/Common/ValidatorExtensions.cs | 36 +-------- src/WebApi/Endpoints/EndpointExtensions.cs | 4 + .../Persistence/ApplicationDbContext.cs | 19 +++++ ...20240803173803_AddCarsAndUsers.Designer.cs | 78 +++++++++++++++++++ .../20240803173803_AddCarsAndUsers.cs | 60 ++++++++++++++ .../ApplicationDbContextModelSnapshot.cs | 75 ++++++++++++++++++ src/WebApi/Users/User.cs | 10 +++ src/WebApi/Users/UserTableConfiguration.cs | 12 +++ src/WebApi/WebApi.csproj | 6 ++ src/WebApi/appsettings.Development.json | 3 + 22 files changed, 688 insertions(+), 46 deletions(-) create mode 100644 Create-MigrationScript.ps1 create mode 100644 migrations/migration.sql create mode 100644 src/WebApi/Authentication/UserAccessor.cs create mode 100644 src/WebApi/Cars/DeleteCar.cs create mode 100644 src/WebApi/Cars/GetCar.cs create mode 100644 src/WebApi/Cars/GetCars.cs create mode 100644 src/WebApi/Cars/UpdateCar.cs create mode 100644 src/WebApi/Common/FluentValidationOptions.cs create mode 100644 src/WebApi/Persistence/ApplicationDbContext.cs create mode 100644 src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.Designer.cs create mode 100644 src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.cs create mode 100644 src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs create mode 100644 src/WebApi/Users/User.cs create mode 100644 src/WebApi/Users/UserTableConfiguration.cs diff --git a/Create-MigrationScript.ps1 b/Create-MigrationScript.ps1 new file mode 100644 index 0000000..823b10e --- /dev/null +++ b/Create-MigrationScript.ps1 @@ -0,0 +1 @@ +dotnet ef migrations script --idempotent --project .\src\WebApi\WebApi.csproj -o migrations/migration.sql diff --git a/migrations/migration.sql b/migrations/migration.sql new file mode 100644 index 0000000..f4ef165 --- /dev/null +++ b/migrations/migration.sql @@ -0,0 +1,48 @@ +CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( + "MigrationId" character varying(150) NOT NULL, + "ProductVersion" character varying(32) NOT NULL, + CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId") +); + +START TRANSACTION; + + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240803173803_AddCarsAndUsers') THEN + CREATE TABLE "Users" ( + "Id" text NOT NULL, + CONSTRAINT "PK_Users" PRIMARY KEY ("Id") + ); + END IF; +END $EF$; + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240803173803_AddCarsAndUsers') THEN + CREATE TABLE "Cars" ( + "Id" uuid NOT NULL, + "Name" character varying(50) NOT NULL, + "UserId" text NOT NULL, + CONSTRAINT "PK_Cars" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Cars_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "Users" ("Id") ON DELETE CASCADE + ); + END IF; +END $EF$; + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240803173803_AddCarsAndUsers') THEN + CREATE INDEX "IX_Cars_UserId" ON "Cars" ("UserId"); + END IF; +END $EF$; + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240803173803_AddCarsAndUsers') THEN + INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") + VALUES ('20240803173803_AddCarsAndUsers', '8.0.7'); + END IF; +END $EF$; +COMMIT; + diff --git a/src/WebApi/Authentication/UserAccessor.cs b/src/WebApi/Authentication/UserAccessor.cs new file mode 100644 index 0000000..6e69d1a --- /dev/null +++ b/src/WebApi/Authentication/UserAccessor.cs @@ -0,0 +1,78 @@ +using Microsoft.Extensions.Options; +using System.Diagnostics.CodeAnalysis; +using System.Security.Claims; + +namespace Vegasco.WebApi.Authentication; + +public sealed class UserAccessor +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IOptions _jwtOptions; + + /// + /// Stores the username upon first retrieval + /// + private string? _cachedUsername; + + /// + /// Stores the id upon first retrieval + /// + private string? _cachedId; + + public UserAccessor(IHttpContextAccessor httpContextAccessor, IOptions jwtOptions) + { + _httpContextAccessor = httpContextAccessor; + _jwtOptions = jwtOptions; + } + + public string GetUsername() + { + if (string.IsNullOrEmpty(_cachedUsername)) + { + _cachedUsername = GetClaimValue(_jwtOptions.Value.NameClaimType ?? ClaimTypes.Name); + } + + return _cachedUsername; + } + + public string GetUserId() + { + if (string.IsNullOrEmpty(_cachedId)) + { + _cachedId = GetClaimValue(ClaimTypes.NameIdentifier); + } + + return _cachedId; + } + + private string GetClaimValue(string claimType) + { + var httpContext = _httpContextAccessor.HttpContext; + + if (httpContext is null) + { + ThrowForMissingHttpContext(); + } + + var claimValue = httpContext.User.FindFirstValue(claimType); + + if (string.IsNullOrWhiteSpace(claimValue)) + { + ThrowForMissingClaim(claimType); + } + + return claimValue; + } + + [DoesNotReturn] + private static void ThrowForMissingHttpContext() + { + throw new InvalidOperationException("No HttpContext available."); + } + + [DoesNotReturn] + private static void ThrowForMissingClaim(string claimType) + { + throw new InvalidOperationException($"No claim of type '{claimType}' found on the current user."); + } +} diff --git a/src/WebApi/Cars/Car.cs b/src/WebApi/Cars/Car.cs index da1e72b..da27e1d 100644 --- a/src/WebApi/Cars/Car.cs +++ b/src/WebApi/Cars/Car.cs @@ -1,4 +1,8 @@ -namespace Vegasco.WebApi.Cars; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Vegasco.WebApi.Users; + +namespace Vegasco.WebApi.Cars; public class Car { @@ -6,5 +10,27 @@ public class Car public string Name { get; set; } = ""; - public Guid UserId { get; set; } + public string UserId { get; set; } = ""; + + public virtual User User { get; set; } = null!; +} + +public class CarTableConfiguration : IEntityTypeConfiguration +{ + public const int NameMaxLength = 50; + + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + + builder.Property(x => x.Name) + .IsRequired() + .HasMaxLength(NameMaxLength); + + builder.Property(x => x.UserId) + .IsRequired(); + + builder.HasOne(x => x.User) + .WithMany(x => x.Cars); + } } diff --git a/src/WebApi/Cars/CreateCar.cs b/src/WebApi/Cars/CreateCar.cs index ed7630e..2bbbb74 100644 --- a/src/WebApi/Cars/CreateCar.cs +++ b/src/WebApi/Cars/CreateCar.cs @@ -1,6 +1,9 @@ using FluentValidation; using FluentValidation.Results; +using Vegasco.WebApi.Authentication; using Vegasco.WebApi.Common; +using Vegasco.WebApi.Persistence; +using Vegasco.WebApi.Users; namespace Vegasco.WebApi.Cars; @@ -12,7 +15,7 @@ public static class CreateCar public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) { return builder - .MapPost("cars", Handler) + .MapPost("cars", Endpoint) .WithTags("Cars"); } @@ -21,18 +24,46 @@ public static class CreateCar public Validator() { RuleFor(x => x.Name) - .NotEmpty(); + .NotEmpty() + .MaximumLength(CarTableConfiguration.NameMaxLength); } } - public static async Task Handler(Request request, IEnumerable> validators) + public static async Task Endpoint( + Request request, + IEnumerable> validators, + ApplicationDbContext dbContext, + UserAccessor userAccessor, + CancellationToken cancellationToken) { - List failedValidations = await validators.ValidateAllAsync(request); + List failedValidations = await validators.ValidateAllAsync(request, cancellationToken: cancellationToken); if (failedValidations.Count > 0) { - return Results.BadRequest(new HttpValidationProblemDetails(failedValidations.ToCombinedDictionary())); + return TypedResults.BadRequest(new HttpValidationProblemDetails(failedValidations.ToCombinedDictionary())); } - return Results.Ok(); + var userId = userAccessor.GetUserId(); + + var user = await dbContext.Users.FindAsync([userId], cancellationToken: cancellationToken); + if (user is null) + { + user = new User + { + Id = userId + }; + await dbContext.Users.AddAsync(user, cancellationToken); + } + + Car car = new() + { + Name = request.Name, + UserId = userId + }; + + await dbContext.Cars.AddAsync(car, cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + + Response response = new(car.Id, car.Name); + return TypedResults.Created($"/v1/cars/{car.Id}", response); } } diff --git a/src/WebApi/Cars/DeleteCar.cs b/src/WebApi/Cars/DeleteCar.cs new file mode 100644 index 0000000..9febdd8 --- /dev/null +++ b/src/WebApi/Cars/DeleteCar.cs @@ -0,0 +1,31 @@ +using Vegasco.WebApi.Persistence; + +namespace Vegasco.WebApi.Cars; + +public static class DeleteCar +{ + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapDelete("cars/{id:guid}", Endpoint) + .WithTags("Cars"); + } + + public static async Task Endpoint( + Guid id, + ApplicationDbContext dbContext, + CancellationToken cancellationToken) + { + var car = await dbContext.Cars.FindAsync([id], cancellationToken: cancellationToken); + + if (car is null) + { + return TypedResults.NotFound(); + } + + dbContext.Cars.Remove(car); + await dbContext.SaveChangesAsync(cancellationToken); + + return TypedResults.NoContent(); + } +} \ No newline at end of file diff --git a/src/WebApi/Cars/GetCar.cs b/src/WebApi/Cars/GetCar.cs new file mode 100644 index 0000000..ef954ef --- /dev/null +++ b/src/WebApi/Cars/GetCar.cs @@ -0,0 +1,31 @@ +using Vegasco.WebApi.Persistence; + +namespace Vegasco.WebApi.Cars; + +public static class GetCar +{ + public record Response(Guid Id, string Name); + + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapGet("cars/{id:guid}", Endpoint) + .WithTags("Cars"); + } + + public static async Task Endpoint( + Guid id, + ApplicationDbContext dbContext, + CancellationToken cancellationToken) + { + var car = await dbContext.Cars.FindAsync([id], cancellationToken: cancellationToken); + + if (car is null) + { + return TypedResults.NotFound(); + } + + var response = new Response(car.Id, car.Name); + return TypedResults.Ok(response); + } +} diff --git a/src/WebApi/Cars/GetCars.cs b/src/WebApi/Cars/GetCars.cs new file mode 100644 index 0000000..9a8ec8b --- /dev/null +++ b/src/WebApi/Cars/GetCars.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using Vegasco.WebApi.Persistence; + +namespace Vegasco.WebApi.Cars; + +public static class GetCars +{ + public record Response(Guid Id, string Name); + + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapGet("cars", Endpoint) + .WithTags("Cars"); + } + + public static async Task Endpoint( + ApplicationDbContext dbContext, + CancellationToken cancellationToken) + { + var cars = await dbContext.Cars + .Select(x => new Response(x.Id, x.Name)) + .ToListAsync(cancellationToken); + + return TypedResults.Ok(cars); + } +} diff --git a/src/WebApi/Cars/UpdateCar.cs b/src/WebApi/Cars/UpdateCar.cs new file mode 100644 index 0000000..6d5ae6b --- /dev/null +++ b/src/WebApi/Cars/UpdateCar.cs @@ -0,0 +1,58 @@ +using FluentValidation; +using FluentValidation.Results; +using Vegasco.WebApi.Authentication; +using Vegasco.WebApi.Common; +using Vegasco.WebApi.Persistence; + +namespace Vegasco.WebApi.Cars; + +public static class UpdateCar +{ + public record Request(string Name); + public record Response(Guid Id, string Name); + + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapPut("cars/{id:guid}", Endpoint) + .WithTags("Cars"); + } + + public class Validator : AbstractValidator + { + public Validator() + { + RuleFor(x => x.Name) + .NotEmpty() + .MaximumLength(CarTableConfiguration.NameMaxLength); + } + } + + public static async Task Endpoint( + Guid id, + Request request, + IEnumerable> validators, + ApplicationDbContext dbContext, + UserAccessor userAccessor, + CancellationToken cancellationToken) + { + List failedValidations = await validators.ValidateAllAsync(request, cancellationToken); + if (failedValidations.Count > 0) + { + return TypedResults.BadRequest(new HttpValidationProblemDetails(failedValidations.ToCombinedDictionary())); + } + + var car = await dbContext.Cars.FindAsync([id], cancellationToken: cancellationToken); + + if (car is null) + { + return TypedResults.NotFound(); + } + + car.Name = request.Name; + await dbContext.SaveChangesAsync(cancellationToken); + + Response response = new(car.Id, car.Name); + return TypedResults.Ok(response); + } +} diff --git a/src/WebApi/Common/DependencyInjectionExtensions.cs b/src/WebApi/Common/DependencyInjectionExtensions.cs index 19de60f..bbcaeee 100644 --- a/src/WebApi/Common/DependencyInjectionExtensions.cs +++ b/src/WebApi/Common/DependencyInjectionExtensions.cs @@ -1,12 +1,14 @@ using Asp.Versioning; using FluentValidation; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using OpenTelemetry.Trace; using System.Diagnostics; using Vegasco.WebApi.Authentication; using Vegasco.WebApi.Endpoints; using Vegasco.WebApi.Endpoints.OpenApi; +using Vegasco.WebApi.Persistence; namespace Vegasco.WebApi.Common; @@ -16,14 +18,15 @@ public static class DependencyInjectionExtensions /// Adds all the WebApi related services to the Dependency Injection container. /// /// - public static void AddWebApiServices(this IServiceCollection services) + public static void AddWebApiServices(this IServiceCollection services, IConfiguration configuration) { services .AddMiscellaneousServices() .AddOpenApi() .AddApiVersioning() .AddOtel() - .AddAuthenticationAndAuthorization(); + .AddAuthenticationAndAuthorization() + .AddDbContext(configuration); } private static IServiceCollection AddMiscellaneousServices(this IServiceCollection services) @@ -38,6 +41,8 @@ public static class DependencyInjectionExtensions services.AddHealthChecks(); services.AddEndpointsFromAssemblyContaining(); + services.AddHttpContextAccessor(); + return services; } @@ -46,7 +51,27 @@ public static class DependencyInjectionExtensions services.ConfigureOptions(); services.AddEndpointsApiExplorer(); - services.AddSwaggerGen(); + services.AddSwaggerGen(o => + { + o.CustomSchemaIds(type => + { + if (string.IsNullOrEmpty(type.FullName)) + { + return type.Name; + } + + var fullClassName = type.FullName; + + if (!string.IsNullOrEmpty(type.Namespace)) + { + fullClassName = fullClassName + .Replace(type.Namespace, "") + .TrimStart('.'); + } + + return fullClassName; + }); + }); return services; } @@ -119,6 +144,21 @@ public static class DependencyInjectionExtensions .RequireAuthenticatedUser() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)); + services.AddScoped(); + + return services; + } + + private static IServiceCollection AddDbContext(this IServiceCollection services, IConfiguration configuration) + { + services.AddDbContext(o => + { + o.UseNpgsql(configuration.GetConnectionString("Database"), c => + { + c.EnableRetryOnFailure(); + }); + }); + return services; } } diff --git a/src/WebApi/Common/FluentValidationOptions.cs b/src/WebApi/Common/FluentValidationOptions.cs new file mode 100644 index 0000000..141e2e7 --- /dev/null +++ b/src/WebApi/Common/FluentValidationOptions.cs @@ -0,0 +1,36 @@ +using FluentValidation; +using Microsoft.Extensions.Options; + +namespace Vegasco.WebApi.Common; + +public class FluentValidationOptions : IValidateOptions + where TOptions : class +{ + private readonly IEnumerable> _validators; + + public string? Name { get; set; } + + public FluentValidationOptions(string? name, IEnumerable> validators) + { + Name = name; + _validators = validators; + } + + public ValidateOptionsResult Validate(string? name, TOptions options) + { + if (name is not null && name != Name) + { + return ValidateOptionsResult.Skip; + } + + ArgumentNullException.ThrowIfNull(options); + + var failedValidations = _validators.ValidateAllAsync(options).Result; + if (failedValidations.Count == 0) + { + return ValidateOptionsResult.Success; + } + + return ValidateOptionsResult.Fail(failedValidations.SelectMany(x => x.Errors.Select(x => x.ErrorMessage))); + } +} \ No newline at end of file diff --git a/src/WebApi/Common/StartupExtensions.cs b/src/WebApi/Common/StartupExtensions.cs index 2e1b332..4b6b07d 100644 --- a/src/WebApi/Common/StartupExtensions.cs +++ b/src/WebApi/Common/StartupExtensions.cs @@ -11,7 +11,7 @@ internal static class StartupExtensions { builder.Configuration.AddEnvironmentVariables("Vegasco_"); - builder.Services.AddWebApiServices(); + builder.Services.AddWebApiServices(builder.Configuration); WebApplication app = builder.Build(); return app; diff --git a/src/WebApi/Common/ValidatorExtensions.cs b/src/WebApi/Common/ValidatorExtensions.cs index 2ee107f..64e04f5 100644 --- a/src/WebApi/Common/ValidatorExtensions.cs +++ b/src/WebApi/Common/ValidatorExtensions.cs @@ -13,10 +13,10 @@ public static class ValidatorExtensions /// /// /// The failed validation results. - public static async Task> ValidateAllAsync(this IEnumerable> validators, T instance) + public static async Task> ValidateAllAsync(this IEnumerable> validators, T instance, CancellationToken cancellationToken = default) { var validationTasks = validators - .Select(validator => validator.ValidateAsync(instance)) + .Select(validator => validator.ValidateAsync(instance, cancellationToken)) .ToList(); await Task.WhenAll(validationTasks); @@ -60,35 +60,3 @@ public static class ValidatorExtensions return builder; } } - -internal class FluentValidationOptions : IValidateOptions - where TOptions : class -{ - private readonly IEnumerable> _validators; - - public string? Name { get; set; } - - public FluentValidationOptions(string? name, IEnumerable> validators) - { - Name = name; - _validators = validators; - } - - public ValidateOptionsResult Validate(string? name, TOptions options) - { - if (name is not null && name != Name) - { - return ValidateOptionsResult.Skip; - } - - ArgumentNullException.ThrowIfNull(options); - - var failedValidations = _validators.ValidateAllAsync(options).Result; - if (failedValidations.Count == 0) - { - return ValidateOptionsResult.Success; - } - - return ValidateOptionsResult.Fail(failedValidations.SelectMany(x => x.Errors.Select(x => x.ErrorMessage))); - } -} \ No newline at end of file diff --git a/src/WebApi/Endpoints/EndpointExtensions.cs b/src/WebApi/Endpoints/EndpointExtensions.cs index 17cc430..28f7df1 100644 --- a/src/WebApi/Endpoints/EndpointExtensions.cs +++ b/src/WebApi/Endpoints/EndpointExtensions.cs @@ -34,6 +34,10 @@ public static class EndpointExtensions .WithApiVersionSet(apiVersionSet) .RequireAuthorization(Constants.Authorization.RequireAuthenticatedUserPolicy); + GetCar.MapEndpoint(versionedApis); + GetCars.MapEndpoint(versionedApis); CreateCar.MapEndpoint(versionedApis); + UpdateCar.MapEndpoint(versionedApis); + DeleteCar.MapEndpoint(versionedApis); } } diff --git a/src/WebApi/Persistence/ApplicationDbContext.cs b/src/WebApi/Persistence/ApplicationDbContext.cs new file mode 100644 index 0000000..e02de1d --- /dev/null +++ b/src/WebApi/Persistence/ApplicationDbContext.cs @@ -0,0 +1,19 @@ +using Microsoft.EntityFrameworkCore; +using Vegasco.WebApi.Cars; +using Vegasco.WebApi.Common; +using Vegasco.WebApi.Users; + +namespace Vegasco.WebApi.Persistence; + +public class ApplicationDbContext(DbContextOptions options) : DbContext(options) +{ + public DbSet Cars { get; set; } + + public DbSet Users { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + modelBuilder.ApplyConfigurationsFromAssembly(typeof(IWebApiMarker).Assembly); + } +} diff --git a/src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.Designer.cs b/src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.Designer.cs new file mode 100644 index 0000000..e4149a5 --- /dev/null +++ b/src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.Designer.cs @@ -0,0 +1,78 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Vegasco.WebApi.Persistence; + +#nullable disable + +namespace Vegasco.WebApi.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20240803173803_AddCarsAndUsers")] + partial class AddCarsAndUsers + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Cars"); + }); + + modelBuilder.Entity("Vegasco.WebApi.Users.User", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => + { + b.HasOne("Vegasco.WebApi.Users.User", "User") + .WithMany("Cars") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Vegasco.WebApi.Users.User", b => + { + b.Navigation("Cars"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.cs b/src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.cs new file mode 100644 index 0000000..a3cceaf --- /dev/null +++ b/src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.cs @@ -0,0 +1,60 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Vegasco.WebApi.Persistence.Migrations +{ + /// + public partial class AddCarsAndUsers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Cars", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + UserId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Cars", x => x.Id); + table.ForeignKey( + name: "FK_Cars_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Cars_UserId", + table: "Cars", + column: "UserId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Cars"); + + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000..c29b621 --- /dev/null +++ b/src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,75 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Vegasco.WebApi.Persistence; + +#nullable disable + +namespace Vegasco.WebApi.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Cars"); + }); + + modelBuilder.Entity("Vegasco.WebApi.Users.User", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => + { + b.HasOne("Vegasco.WebApi.Users.User", "User") + .WithMany("Cars") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Vegasco.WebApi.Users.User", b => + { + b.Navigation("Cars"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/WebApi/Users/User.cs b/src/WebApi/Users/User.cs new file mode 100644 index 0000000..cc5a46d --- /dev/null +++ b/src/WebApi/Users/User.cs @@ -0,0 +1,10 @@ +using Vegasco.WebApi.Cars; + +namespace Vegasco.WebApi.Users; + +public class User +{ + public string Id { get; set; } = ""; + + public virtual IList Cars { get; set; } = []; +} diff --git a/src/WebApi/Users/UserTableConfiguration.cs b/src/WebApi/Users/UserTableConfiguration.cs new file mode 100644 index 0000000..cf5a29a --- /dev/null +++ b/src/WebApi/Users/UserTableConfiguration.cs @@ -0,0 +1,12 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Vegasco.WebApi.Users; + +public class UserTableConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(user => user.Id); + } +} diff --git a/src/WebApi/WebApi.csproj b/src/WebApi/WebApi.csproj index aaa80ac..9790ef8 100644 --- a/src/WebApi/WebApi.csproj +++ b/src/WebApi/WebApi.csproj @@ -16,7 +16,13 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + diff --git a/src/WebApi/appsettings.Development.json b/src/WebApi/appsettings.Development.json index 0c208ae..e66cc17 100644 --- a/src/WebApi/appsettings.Development.json +++ b/src/WebApi/appsettings.Development.json @@ -1,4 +1,7 @@ { + "ConnectionStrings": { + "Database": "Host=localhost;Port=5432;Database=postgres;Username=postgres;Password=postgres" + }, "Logging": { "LogLevel": { "Default": "Information", -- 2.49.1 From ff2da66a2235d6144d0f08c7fe8e06650e53e489 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 17 Aug 2024 16:38:40 +0200 Subject: [PATCH 004/150] Add unit tests --- .../Authentication/UserAccessorTests.cs | 179 ++++++++++++++++++ .../Cars/CreateCarRequestValidatorTests.cs | 71 +++++++ .../Cars/UpdateCarRequestValidatorTests.cs | 71 +++++++ .../WebApi.Tests.Unit.csproj | 35 ++++ vegasco-server.sln | 9 + 5 files changed, 365 insertions(+) create mode 100644 tests/WebApi.Tests.Unit/Authentication/UserAccessorTests.cs create mode 100644 tests/WebApi.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs create mode 100644 tests/WebApi.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs create mode 100644 tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj diff --git a/tests/WebApi.Tests.Unit/Authentication/UserAccessorTests.cs b/tests/WebApi.Tests.Unit/Authentication/UserAccessorTests.cs new file mode 100644 index 0000000..c32f769 --- /dev/null +++ b/tests/WebApi.Tests.Unit/Authentication/UserAccessorTests.cs @@ -0,0 +1,179 @@ +using FluentAssertions; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using NSubstitute; +using System.Security.Claims; +using Vegasco.WebApi.Authentication; + +namespace WebApi.Tests.Unit.Authentication; +public sealed class UserAccessorTests +{ + private readonly UserAccessor _sut; + private readonly IHttpContextAccessor _httpContextAccessor; + + private static readonly string _nameClaimType = "name"; + private readonly JwtOptions _jwtOptions = new() + { + NameClaimType = _nameClaimType + }; + + private readonly IOptions _options = Substitute.For>(); + + private static readonly string _defaultUsername = "username"; + private static readonly string _defaultId = "id"; + private readonly ClaimsPrincipal _defaultUser = new(new ClaimsIdentity( + [ + new Claim(_nameClaimType, _defaultUsername), + new Claim(ClaimTypes.NameIdentifier, _defaultId) + ])); + + public UserAccessorTests() + { + _httpContextAccessor = new HttpContextAccessor + { + HttpContext = new DefaultHttpContext() + { + User = _defaultUser + } + }; + + _options.Value.Returns(_jwtOptions); + + _sut = new UserAccessor(_httpContextAccessor, _options); + } + + #region GetUsername + + [Fact] + public void GetUsername_ShouldReturnUsername_WhenOptionsNameClaimTypeMatches() + { + // Arrange + + // Act + var result = _sut.GetUsername(); + + // Assert + result.Should().Be(_defaultUsername); + } + + [Fact] + public void GetUsername_ShouldReturnUsername_WhenNameClaimTypeIsNotSetAndUsernameIsInUriNameClaimType() + { + // Arrange + _jwtOptions.NameClaimType = null; + _httpContextAccessor.HttpContext!.User = new ClaimsPrincipal(new ClaimsIdentity( + [ + new Claim(ClaimTypes.Name, _defaultUsername) + ])); + + // Act + var result = _sut.GetUsername(); + + // Assert + result.Should().Be(_defaultUsername); + } + + [Fact] + public void GetUsername_ShouldCacheUsername_WhenFirstCalled() + { + // Arrange + _ = _sut.GetUsername(); + _options.ClearReceivedCalls(); + + // Act + var result = _sut.GetUsername(); + + // Assert + result.Should().Be(_defaultUsername); + _ = _options.Received(0).Value; + } + + [Fact] + public void GetUsername_ShouldThrowInvalidOperationException_WhenHttpContextIsNull() + { + // Arrange + _httpContextAccessor.HttpContext = null; + + // Act + var action = () => _sut.GetUsername(); + + // Assert + action.Should().ThrowExactly() + .Which.Message.Should().Be("No HttpContext available."); + } + + [Fact] + public void GetUsername_ShouldThrowInvalidOperationException_WhenNameClaimIsNotFound() + { + // Arrange + _httpContextAccessor.HttpContext!.User = new ClaimsPrincipal(); + + // Act + var action = () => _sut.GetUsername(); + + // Assert + action.Should().ThrowExactly() + .Which.Message.Should().Be($"No claim of type '{_nameClaimType}' found on the current user."); + } + + #endregion + + #region GetUserId + + [Fact] + public void GetUserId_ShouldReturnUserId_WhenUserIdClaimExists() + { + // Arrange + + // Act + var result = _sut.GetUserId(); + + // Assert + result.Should().Be(_defaultId); + } + + [Fact] + public void GetUserId_ShouldCacheUserId_WhenFirstCalled() + { + // Arrange + _ = _sut.GetUserId(); + _options.ClearReceivedCalls(); + + // Act + var result = _sut.GetUserId(); + + // Assert + result.Should().Be(_defaultId); + _ = _options.Received(0).Value; + } + + [Fact] + public void GetUserId_ShouldThrowInvalidOperationException_WhenHttpContextIsNull() + { + // Arrange + _httpContextAccessor.HttpContext = null; + + // Act + var action = () => _sut.GetUserId(); + + // Assert + action.Should().ThrowExactly() + .Which.Message.Should().Be("No HttpContext available."); + } + + [Fact] + public void GetUserId_ShouldThrowInvalidOperationException_WhenIdClaimIsNotFound() + { + // Arrange + _httpContextAccessor.HttpContext!.User = new ClaimsPrincipal(); + + // Act + var action = () => _sut.GetUserId(); + + // Assert + action.Should().ThrowExactly() + .Which.Message.Should().Be($"No claim of type '{ClaimTypes.NameIdentifier}' found on the current user."); + } + + #endregion +} diff --git a/tests/WebApi.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs b/tests/WebApi.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs new file mode 100644 index 0000000..127da32 --- /dev/null +++ b/tests/WebApi.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs @@ -0,0 +1,71 @@ +using FluentAssertions; +using Vegasco.WebApi.Cars; + +namespace WebApi.Tests.Unit.Cars; + +public sealed class CreateCarRequestValidatorTests +{ + private readonly CreateCar.Validator _sut = new(); + + private readonly CreateCar.Request _validRequest = new("Ford Focus"); + + [Fact] + public async Task ValidateAsync_ShouldBeValid_WhenRequestIsValid() + { + // Arrange + + // Act + var result = await _sut.ValidateAsync(_validRequest); + + // Assert + result.IsValid.Should().BeTrue(); + } + + [Theory] + [InlineData(1)] + [InlineData(50)] + public async Task ValidateAsync_ShouldBeValid_WhenNameIsJustWithinTheLimits(int nameLength) + { + // Arrange + var request = _validRequest with { Name = new string('s', nameLength) }; + + // Act + var result = await _sut.ValidateAsync(request); + + // Assert + result.IsValid.Should().BeTrue(); + } + + [Fact] + public async Task ValidateAsync_ShouldNotBeValid_WhenNameIsEmpty() + { + // Arrange + var request = _validRequest with { Name = "" }; + + // Act + var result = await _sut.ValidateAsync(request); + + // Assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().ContainSingle() + .Which + .PropertyName.Should().Be(nameof(CreateCar.Request.Name)); + } + + [Fact] + public async Task ValidateAsync_ShouldNotBeValid_WhenNameIsTooLong() + { + // Arrange + const int nameMaxLength = 50; + var request = _validRequest with { Name = new string('s', nameMaxLength + 1) }; + + // Act + var result = await _sut.ValidateAsync(request); + + // Assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().ContainSingle() + .Which + .PropertyName.Should().Be(nameof(CreateCar.Request.Name)); + } +} diff --git a/tests/WebApi.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs b/tests/WebApi.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs new file mode 100644 index 0000000..8e2d1e0 --- /dev/null +++ b/tests/WebApi.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs @@ -0,0 +1,71 @@ +using FluentAssertions; +using Vegasco.WebApi.Cars; + +namespace WebApi.Tests.Unit.Cars; + +public sealed class UpdateCarRequestValidatorTests +{ + private readonly UpdateCar.Validator _sut = new(); + + private readonly UpdateCar.Request _validRequest = new("Ford Focus"); + + [Fact] + public async Task ValidateAsync_ShouldBeValid_WhenRequestIsValid() + { + // Arrange + + // Act + var result = await _sut.ValidateAsync(_validRequest); + + // Assert + result.IsValid.Should().BeTrue(); + } + + [Theory] + [InlineData(1)] + [InlineData(50)] + public async Task ValidateAsync_ShouldBeValid_WhenNameIsJustWithinTheLimits(int nameLength) + { + // Arrange + var request = _validRequest with { Name = new string('s', nameLength) }; + + // Act + var result = await _sut.ValidateAsync(request); + + // Assert + result.IsValid.Should().BeTrue(); + } + + [Fact] + public async Task ValidateAsync_ShouldNotBeValid_WhenNameIsEmpty() + { + // Arrange + var request = _validRequest with { Name = "" }; + + // Act + var result = await _sut.ValidateAsync(request); + + // Assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().ContainSingle() + .Which + .PropertyName.Should().Be(nameof(UpdateCar.Request.Name)); + } + + [Fact] + public async Task ValidateAsync_ShouldNotBeValid_WhenNameIsTooLong() + { + // Arrange + const int nameMaxLength = 50; + var request = _validRequest with { Name = new string('s', nameMaxLength + 1) }; + + // Act + var result = await _sut.ValidateAsync(request); + + // Assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().ContainSingle() + .Which + .PropertyName.Should().Be(nameof(UpdateCar.Request.Name)); + } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj b/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj new file mode 100644 index 0000000..2f6089a --- /dev/null +++ b/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj @@ -0,0 +1,35 @@ + + + + net8.0 + enable + enable + + false + true + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/vegasco-server.sln b/vegasco-server.sln index da38768..4ea8158 100644 --- a/vegasco-server.sln +++ b/vegasco-server.sln @@ -12,6 +12,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{437DE053-1DAB-4EEF-BEA6-E3B5179692F8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.Unit", "tests\WebApi.Tests.Unit\WebApi.Tests.Unit.csproj", "{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -22,12 +26,17 @@ Global {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Debug|Any CPU.Build.0 = Debug|Any CPU {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Release|Any CPU.ActiveCfg = Release|Any CPU {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Release|Any CPU.Build.0 = Release|Any CPU + {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {9FF3C98A-5085-4EBE-A980-DB2148B0C00A} = {C051A684-BD6A-43F2-B0CC-F3C2315D99E3} + {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7813E32D-AE19-479C-853B-063882D2D05A} -- 2.49.1 From 19b105b0e86aa3c9f4bc454bdf8f7f9a6b056cd1 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 17 Aug 2024 16:38:40 +0200 Subject: [PATCH 005/150] Add integration tests --- src/WebApi/Cars/GetCars.cs | 2 +- tests/WebApi.Tests.Integration/CarFaker.cs | 19 ++++ .../Cars/CreateCarTests.cs | 70 +++++++++++++ .../Cars/DeleteCarTests.cs | 64 ++++++++++++ .../Cars/GetCarTests.cs | 57 +++++++++++ .../Cars/GetCarsTests.cs | 66 +++++++++++++ .../Cars/UpdateCarTests.cs | 99 +++++++++++++++++++ .../PostgresRespawner.cs | 40 ++++++++ .../SharedTestCollection.cs | 7 ++ ...TestUserAlwaysAuthorizedPolicyEvaluator.cs | 38 +++++++ .../WebApi.Tests.Integration.csproj | 32 ++++++ .../WebApi.Tests.Integration/WebAppFactory.cs | 79 +++++++++++++++ vegasco-server.sln | 9 +- vegasco-server.sln.DotSettings | 3 + 14 files changed, 583 insertions(+), 2 deletions(-) create mode 100644 tests/WebApi.Tests.Integration/CarFaker.cs create mode 100644 tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs create mode 100644 tests/WebApi.Tests.Integration/Cars/DeleteCarTests.cs create mode 100644 tests/WebApi.Tests.Integration/Cars/GetCarTests.cs create mode 100644 tests/WebApi.Tests.Integration/Cars/GetCarsTests.cs create mode 100644 tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs create mode 100644 tests/WebApi.Tests.Integration/PostgresRespawner.cs create mode 100644 tests/WebApi.Tests.Integration/SharedTestCollection.cs create mode 100644 tests/WebApi.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs create mode 100644 tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj create mode 100644 tests/WebApi.Tests.Integration/WebAppFactory.cs create mode 100644 vegasco-server.sln.DotSettings diff --git a/src/WebApi/Cars/GetCars.cs b/src/WebApi/Cars/GetCars.cs index 9a8ec8b..027b026 100644 --- a/src/WebApi/Cars/GetCars.cs +++ b/src/WebApi/Cars/GetCars.cs @@ -14,7 +14,7 @@ public static class GetCars .WithTags("Cars"); } - public static async Task Endpoint( + private static async Task Endpoint( ApplicationDbContext dbContext, CancellationToken cancellationToken) { diff --git a/tests/WebApi.Tests.Integration/CarFaker.cs b/tests/WebApi.Tests.Integration/CarFaker.cs new file mode 100644 index 0000000..2d5bb5e --- /dev/null +++ b/tests/WebApi.Tests.Integration/CarFaker.cs @@ -0,0 +1,19 @@ +using Bogus; +using Vegasco.WebApi.Cars; + +namespace WebApi.Tests.Integration; + +internal class CarFaker +{ + private readonly Faker _faker = new(); + + internal CreateCar.Request CreateCarRequest() + { + return new CreateCar.Request(_faker.Vehicle.Model()); + } + + internal UpdateCar.Request UpdateCarRequest() + { + return new UpdateCar.Request(_faker.Vehicle.Model()); + } +} diff --git a/tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs b/tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs new file mode 100644 index 0000000..4376218 --- /dev/null +++ b/tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs @@ -0,0 +1,70 @@ +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using System.Net; +using System.Net.Http.Json; +using Vegasco.WebApi.Cars; +using Vegasco.WebApi.Persistence; + +namespace WebApi.Tests.Integration.Cars; + +[Collection(SharedTestCollection.Name)] +public class CreateCarTests : IAsyncLifetime +{ + private readonly WebAppFactory _factory; + private readonly IServiceScope _scope; + private readonly ApplicationDbContext _dbContext; + + private readonly CarFaker _carFaker = new(); + + public CreateCarTests(WebAppFactory factory) + { + _factory = factory; + _scope = _factory.Services.CreateScope(); + _dbContext = _scope.ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task CreateCar_ShouldCreateCar_WhenRequestIsValid() + { + // Arrange + var createCarRequest = _carFaker.CreateCarRequest(); + + // Act + var response = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.Created); + var createdCar = await response.Content.ReadFromJsonAsync(); + createdCar.Should().BeEquivalentTo(createCarRequest, o => o.ExcludingMissingMembers()); + + _dbContext.Cars.Should().ContainEquivalentOf(createdCar); + } + + [Fact] + public async Task CreateCar_ShouldReturnValidationProblems_WhenRequestIsNotValid() + { + // Arrange + var createCarRequest = new CreateCar.Request(""); + + // Act + var response = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var validationProblemDetails = await response.Content.ReadFromJsonAsync(); + validationProblemDetails!.Errors.Keys.Should().Contain(x => + x.Equals(nameof(CreateCar.Request.Name), StringComparison.OrdinalIgnoreCase)); + + _dbContext.Cars.Should().NotContainEquivalentOf(createCarRequest, o => o.ExcludingMissingMembers()); + } + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + _scope.Dispose(); + await _dbContext.DisposeAsync(); + await _factory.ResetDatabaseAsync(); + } +} diff --git a/tests/WebApi.Tests.Integration/Cars/DeleteCarTests.cs b/tests/WebApi.Tests.Integration/Cars/DeleteCarTests.cs new file mode 100644 index 0000000..e672d0f --- /dev/null +++ b/tests/WebApi.Tests.Integration/Cars/DeleteCarTests.cs @@ -0,0 +1,64 @@ +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using System.Net; +using System.Net.Http.Json; +using Vegasco.WebApi.Cars; +using Vegasco.WebApi.Persistence; + +namespace WebApi.Tests.Integration.Cars; + +[Collection(SharedTestCollection.Name)] +public class DeleteCarTests : IAsyncLifetime +{ + private readonly WebAppFactory _factory; + private readonly IServiceScope _scope; + private readonly ApplicationDbContext _dbContext; + + private readonly CarFaker _carFaker = new(); + + public DeleteCarTests(WebAppFactory factory) + { + _factory = factory; + _scope = _factory.Services.CreateScope(); + _dbContext = _scope.ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task DeleteCar_ShouldReturnNotFound_WhenCarDoesNotExist() + { + // Arrange + var randomCarId = Guid.NewGuid(); + + // Act + var response = await _factory.HttpClient.DeleteAsync($"v1/cars/{randomCarId}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact] + public async Task DeleteCar_ShouldDeleteCar_WhenCarExists() + { + // Arrange + var createCarRequest = _carFaker.CreateCarRequest(); + var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + createCarResponse.EnsureSuccessStatusCode(); + var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); + + // Act + var response = await _factory.HttpClient.DeleteAsync($"v1/cars/{createdCar!.Id}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NoContent); + _dbContext.Cars.Should().BeEmpty(); + } + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + _scope.Dispose(); + await _dbContext.DisposeAsync(); + await _factory.ResetDatabaseAsync(); + } +} diff --git a/tests/WebApi.Tests.Integration/Cars/GetCarTests.cs b/tests/WebApi.Tests.Integration/Cars/GetCarTests.cs new file mode 100644 index 0000000..af2dcfa --- /dev/null +++ b/tests/WebApi.Tests.Integration/Cars/GetCarTests.cs @@ -0,0 +1,57 @@ +using FluentAssertions; +using System.Net; +using System.Net.Http.Json; +using Vegasco.WebApi.Cars; + +namespace WebApi.Tests.Integration.Cars; + +[Collection(SharedTestCollection.Name)] +public class GetCarTests : IAsyncLifetime +{ + private readonly WebAppFactory _factory; + + private readonly CarFaker _carFaker = new(); + + public GetCarTests(WebAppFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetCar_ShouldReturnNotFound_WhenCarDoesNotExist() + { + // Arrange + var randomCarId = Guid.NewGuid(); + + // Act + var response = await _factory.HttpClient.GetAsync($"v1/cars/{randomCarId}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact] + public async Task GetCar_ShouldReturnCar_WhenCarExists() + { + // Arrange + var createCarRequest = _carFaker.CreateCarRequest(); + var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + createCarResponse.EnsureSuccessStatusCode(); + var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); + + // Act + var response = await _factory.HttpClient.GetAsync($"v1/cars/{createdCar!.Id}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var car = await response.Content.ReadFromJsonAsync(); + car.Should().BeEquivalentTo(createdCar); + } + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + await _factory.ResetDatabaseAsync(); + } +} diff --git a/tests/WebApi.Tests.Integration/Cars/GetCarsTests.cs b/tests/WebApi.Tests.Integration/Cars/GetCarsTests.cs new file mode 100644 index 0000000..b94c864 --- /dev/null +++ b/tests/WebApi.Tests.Integration/Cars/GetCarsTests.cs @@ -0,0 +1,66 @@ +using FluentAssertions; +using System.Net; +using System.Net.Http.Json; +using Vegasco.WebApi.Cars; + +namespace WebApi.Tests.Integration.Cars; + +[Collection(SharedTestCollection.Name)] +public class GetCarsTests : IAsyncLifetime +{ + private readonly WebAppFactory _factory; + + private readonly CarFaker _carFaker = new(); + + public GetCarsTests(WebAppFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetCars_ShouldReturnEmptyList_WhenNoEntriesExist() + { + // Arrange + + // Act + var response = await _factory.HttpClient.GetAsync("v1/cars"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var cars = await response.Content.ReadFromJsonAsync>(); + cars.Should().BeEmpty(); + } + + [Fact] + public async Task GetCars_ShouldReturnEntries_WhenEntriesExist() + { + // Arrange + List createdCars = []; + + const int numberOfCars = 5; + for (var i = 0; i < numberOfCars; i++) + { + var createCarRequest = _carFaker.CreateCarRequest(); + var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + createCarResponse.EnsureSuccessStatusCode(); + + var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); + createdCars.Add(createdCar!); + } + + // Act + var response = await _factory.HttpClient.GetAsync("v1/cars"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var cars = await response.Content.ReadFromJsonAsync>(); + cars.Should().BeEquivalentTo(createdCars); + } + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + await _factory.ResetDatabaseAsync(); + } +} diff --git a/tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs b/tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs new file mode 100644 index 0000000..bc0ca8b --- /dev/null +++ b/tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs @@ -0,0 +1,99 @@ +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using System.Net; +using System.Net.Http.Json; +using Vegasco.WebApi.Cars; +using Vegasco.WebApi.Persistence; + +namespace WebApi.Tests.Integration.Cars; + +[Collection(SharedTestCollection.Name)] +public class UpdateCarTests : IAsyncLifetime +{ + private readonly WebAppFactory _factory; + private readonly IServiceScope _scope; + private readonly ApplicationDbContext _dbContext; + + private readonly CarFaker _carFaker = new(); + + public UpdateCarTests(WebAppFactory factory) + { + _factory = factory; + _scope = _factory.Services.CreateScope(); + _dbContext = _scope.ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task UpdateCar_ShouldUpdateCar_WhenCarExistsAndRequestIsValid() + { + // Arrange + var createCarRequest = _carFaker.CreateCarRequest(); + var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + createCarResponse.EnsureSuccessStatusCode(); + var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); + + var updateCarRequest = _carFaker.UpdateCarRequest(); + + // Act + var response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{createdCar!.Id}", updateCarRequest); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var updatedCar = await response.Content.ReadFromJsonAsync(); + updatedCar!.Id.Should().Be(createdCar.Id); + updatedCar.Should().BeEquivalentTo(updateCarRequest, o => o.ExcludingMissingMembers()); + + _dbContext.Cars.Should().ContainEquivalentOf(updatedCar, o => o.ExcludingMissingMembers()); + } + + [Fact] + public async Task UpdateCar_ShouldReturnValidationProblems_WhenRequestIsNotValid() + { + // Arrange + var createCarRequest = _carFaker.CreateCarRequest(); + var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + createCarResponse.EnsureSuccessStatusCode(); + var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); + + var updateCarRequest = new UpdateCar.Request(""); + + // Act + var response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{createdCar!.Id}", updateCarRequest); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var validationProblemDetails = await response.Content.ReadFromJsonAsync(); + validationProblemDetails!.Errors.Keys.Should().Contain(x => + x.Equals(nameof(CreateCar.Request.Name), StringComparison.OrdinalIgnoreCase)); + + _dbContext.Cars.Should().ContainSingle(x => x.Id == createdCar.Id) + .Which + .Should().NotBeEquivalentTo(updateCarRequest, o => o.ExcludingMissingMembers()); + } + + [Fact] + public async Task UpdateCar_ShouldReturnNotFound_WhenNoCarWithIdExists() + { + // Arrange + var updateCarRequest = _carFaker.UpdateCarRequest(); + var randomCarId = Guid.NewGuid(); + + // Act + var response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{randomCarId}", updateCarRequest); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + + _dbContext.Cars.Should().BeEmpty(); + } + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + _scope.Dispose(); + await _dbContext.DisposeAsync(); + await _factory.ResetDatabaseAsync(); + } +} diff --git a/tests/WebApi.Tests.Integration/PostgresRespawner.cs b/tests/WebApi.Tests.Integration/PostgresRespawner.cs new file mode 100644 index 0000000..d969aac --- /dev/null +++ b/tests/WebApi.Tests.Integration/PostgresRespawner.cs @@ -0,0 +1,40 @@ +using Npgsql; +using Respawn; +using System.Data.Common; + +namespace WebApi.Tests.Integration; +internal sealed class PostgresRespawner : IDisposable +{ + private readonly DbConnection _connection; + private readonly Respawner _respawner; + + private PostgresRespawner(Respawner respawner, DbConnection connection) + { + _respawner = respawner; + _connection = connection; + } + + public static async Task CreateAsync(string connectionString) + { + DbConnection connection = new NpgsqlConnection(connectionString); + await connection.OpenAsync(); + + var respawner = await Respawner.CreateAsync(connection, + new RespawnerOptions + { + SchemasToInclude = ["public"], + DbAdapter = DbAdapter.Postgres + }); + return new PostgresRespawner(respawner, connection); + } + + public async Task ResetDatabaseAsync() + { + await _respawner.ResetAsync(_connection); + } + + public void Dispose() + { + _connection.Dispose(); + } +} diff --git a/tests/WebApi.Tests.Integration/SharedTestCollection.cs b/tests/WebApi.Tests.Integration/SharedTestCollection.cs new file mode 100644 index 0000000..bb824b2 --- /dev/null +++ b/tests/WebApi.Tests.Integration/SharedTestCollection.cs @@ -0,0 +1,7 @@ +namespace WebApi.Tests.Integration; + +[CollectionDefinition(Name)] +public class SharedTestCollection : ICollectionFixture +{ + public const string Name = nameof(SharedTestCollection); +} diff --git a/tests/WebApi.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs b/tests/WebApi.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs new file mode 100644 index 0000000..ce19bdf --- /dev/null +++ b/tests/WebApi.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization.Policy; +using Microsoft.AspNetCore.Http; +using System.Security.Claims; + +namespace WebApi.Tests.Integration; + +public sealed class TestUserAlwaysAuthorizedPolicyEvaluator : IPolicyEvaluator +{ + public const string Username = "Test user"; + public static readonly string UserId = Guid.NewGuid().ToString(); + + public Task AuthenticateAsync(AuthorizationPolicy policy, HttpContext context) + { + Claim[] claims = + [ + + new Claim(ClaimTypes.Name, Username), + new Claim("name", Username), + new Claim(ClaimTypes.NameIdentifier, UserId), + new Claim("aud", "https://localhost") + ]; + + ClaimsIdentity identity = new(claims, JwtBearerDefaults.AuthenticationScheme); + ClaimsPrincipal principal = new(identity); + AuthenticationTicket ticket = new(principal, JwtBearerDefaults.AuthenticationScheme); + var result = AuthenticateResult.Success(ticket); + return Task.FromResult(result); ; + } + + public Task AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, + object? resource) + { + return Task.FromResult(PolicyAuthorizationResult.Success()); + } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj new file mode 100644 index 0000000..9f191f4 --- /dev/null +++ b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj @@ -0,0 +1,32 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/WebApi.Tests.Integration/WebAppFactory.cs b/tests/WebApi.Tests.Integration/WebAppFactory.cs new file mode 100644 index 0000000..3fb871d --- /dev/null +++ b/tests/WebApi.Tests.Integration/WebAppFactory.cs @@ -0,0 +1,79 @@ +using DotNet.Testcontainers.Images; +using Microsoft.AspNetCore.Authorization.Policy; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Testcontainers.PostgreSql; +using Vegasco.WebApi.Common; +using Vegasco.WebApi.Persistence; + +namespace WebApi.Tests.Integration; + +public sealed class WebAppFactory : WebApplicationFactory, IAsyncLifetime +{ + private readonly PostgreSqlContainer _database = new PostgreSqlBuilder() + .WithImage(DockerImage) + .WithImagePullPolicy(PullPolicy.Always) + .Build(); + + private const string DockerImage = "postgres:16.3-alpine"; + + public HttpClient HttpClient => CreateClient(); + + private PostgresRespawner? _postgresRespawner; + + public async Task InitializeAsync() + { + await _database.StartAsync(); + + // Force application startup (i.e. initialization and validation) + _ = CreateClient(); + + using var scope = Services.CreateScope(); + await using var dbContext = scope.ServiceProvider.GetRequiredService(); + await dbContext.Database.MigrateAsync(); + + _postgresRespawner = await PostgresRespawner.CreateAsync(_database.GetConnectionString()); + } + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + IEnumerable> customConfig = + [ + new KeyValuePair("ConnectionStrings:Database", _database.GetConnectionString()), + new KeyValuePair("JWT:Authority", "https://localhost"), + new KeyValuePair("JWT:Audience", "https://localhost"), + new KeyValuePair("JWT:Issuer", "https://localhost"), + new KeyValuePair("JWT:NameClaimType", null), + ]; + + builder.UseConfiguration(new ConfigurationBuilder() + .AddInMemoryCollection(customConfig) + .Build()); + + builder.ConfigureServices(services => + { + }); + + builder.ConfigureTestServices(services => + { + services.RemoveAll(); + services.AddSingleton(); + }); + } + + public async Task ResetDatabaseAsync() + { + await _postgresRespawner!.ResetDatabaseAsync(); + } + + async Task IAsyncLifetime.DisposeAsync() + { + _postgresRespawner!.Dispose(); + await _database.DisposeAsync(); + } +} \ No newline at end of file diff --git a/vegasco-server.sln b/vegasco-server.sln index 4ea8158..7c09892 100644 --- a/vegasco-server.sln +++ b/vegasco-server.sln @@ -14,7 +14,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{437DE053-1DAB-4EEF-BEA6-E3B5179692F8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.Unit", "tests\WebApi.Tests.Unit\WebApi.Tests.Unit.csproj", "{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests.Unit", "tests\WebApi.Tests.Unit\WebApi.Tests.Unit.csproj", "{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.Integration", "tests\WebApi.Tests.Integration\WebApi.Tests.Integration.csproj", "{0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -30,6 +32,10 @@ Global {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Debug|Any CPU.Build.0 = Debug|Any CPU {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.ActiveCfg = Release|Any CPU {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.Build.0 = Release|Any CPU + {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -37,6 +43,7 @@ Global GlobalSection(NestedProjects) = preSolution {9FF3C98A-5085-4EBE-A980-DB2148B0C00A} = {C051A684-BD6A-43F2-B0CC-F3C2315D99E3} {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} + {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7813E32D-AE19-479C-853B-063882D2D05A} diff --git a/vegasco-server.sln.DotSettings b/vegasco-server.sln.DotSettings new file mode 100644 index 0000000..d8d7c4c --- /dev/null +++ b/vegasco-server.sln.DotSettings @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file -- 2.49.1 From 4be9fd2043aa9ac05b5b8f7444da6e7c051ec9f7 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 17 Aug 2024 16:38:40 +0200 Subject: [PATCH 006/150] Update convenience scripts --- Create-Migration.ps1 | 2 ++ Create-MigrationScript.ps1 | 1 - Run-PostgresDb.ps1 | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 Create-Migration.ps1 delete mode 100644 Create-MigrationScript.ps1 create mode 100644 Run-PostgresDb.ps1 diff --git a/Create-Migration.ps1 b/Create-Migration.ps1 new file mode 100644 index 0000000..729f335 --- /dev/null +++ b/Create-Migration.ps1 @@ -0,0 +1,2 @@ +dotnet ef migrations add $args[0] --project .\src\WebApi\WebApi.csproj --output-dir Persistence/Migrations +dotnet ef migrations script --idempotent --project .\src\WebApi\WebApi.csproj --output migrations/migration.sql diff --git a/Create-MigrationScript.ps1 b/Create-MigrationScript.ps1 deleted file mode 100644 index 823b10e..0000000 --- a/Create-MigrationScript.ps1 +++ /dev/null @@ -1 +0,0 @@ -dotnet ef migrations script --idempotent --project .\src\WebApi\WebApi.csproj -o migrations/migration.sql diff --git a/Run-PostgresDb.ps1 b/Run-PostgresDb.ps1 new file mode 100644 index 0000000..4ad1ab2 --- /dev/null +++ b/Run-PostgresDb.ps1 @@ -0,0 +1 @@ +docker run --rm -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres postgres:16.3-alpine \ No newline at end of file -- 2.49.1 From 1d6ecfee6ed8ec0da547512acacb1ec84d2a5225 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 17 Aug 2024 16:38:40 +0200 Subject: [PATCH 007/150] Require oidc metadata url instead of individual values --- src/WebApi/Authentication/JwtOptions.cs | 13 ++++--------- src/WebApi/Common/DependencyInjectionExtensions.cs | 7 ++----- tests/WebApi.Tests.Integration/WebAppFactory.cs | 5 ++--- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/WebApi/Authentication/JwtOptions.cs b/src/WebApi/Authentication/JwtOptions.cs index e05daa0..01e8223 100644 --- a/src/WebApi/Authentication/JwtOptions.cs +++ b/src/WebApi/Authentication/JwtOptions.cs @@ -6,11 +6,9 @@ public class JwtOptions { public const string SectionName = "JWT"; - public string Audience { get; set; } = ""; + public string ValidAudience { get; set; } = ""; - public string Authority { get; set; } = ""; - - public string Issuer { get; set; } = ""; + public string MetadataUrl { get; set; } = ""; public string? NameClaimType { get; set; } } @@ -19,13 +17,10 @@ public class JwtOptionsValidator : AbstractValidator { public JwtOptionsValidator() { - RuleFor(x => x.Audience) + RuleFor(x => x.ValidAudience) .NotEmpty(); - RuleFor(x => x.Authority) - .NotEmpty(); - - RuleFor(x => x.Issuer) + RuleFor(x => x.MetadataUrl) .NotEmpty(); } } \ No newline at end of file diff --git a/src/WebApi/Common/DependencyInjectionExtensions.cs b/src/WebApi/Common/DependencyInjectionExtensions.cs index bbcaeee..bab1a0d 100644 --- a/src/WebApi/Common/DependencyInjectionExtensions.cs +++ b/src/WebApi/Common/DependencyInjectionExtensions.cs @@ -125,14 +125,11 @@ public static class DependencyInjectionExtensions services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o => { - o.Authority = jwtOptions.Value.Authority; + o.MetadataAddress = jwtOptions.Value.MetadataUrl; - o.TokenValidationParameters.ValidAudience = jwtOptions.Value.Audience; + o.TokenValidationParameters.ValidAudience = jwtOptions.Value.ValidAudience; o.TokenValidationParameters.ValidateAudience = true; - o.TokenValidationParameters.ValidIssuer = jwtOptions.Value.Issuer; - o.TokenValidationParameters.ValidateIssuer = true; - if (!string.IsNullOrWhiteSpace(jwtOptions.Value.NameClaimType)) { o.TokenValidationParameters.NameClaimType = jwtOptions.Value.NameClaimType; diff --git a/tests/WebApi.Tests.Integration/WebAppFactory.cs b/tests/WebApi.Tests.Integration/WebAppFactory.cs index 3fb871d..f5abe9f 100644 --- a/tests/WebApi.Tests.Integration/WebAppFactory.cs +++ b/tests/WebApi.Tests.Integration/WebAppFactory.cs @@ -45,9 +45,8 @@ public sealed class WebAppFactory : WebApplicationFactory, IAsync IEnumerable> customConfig = [ new KeyValuePair("ConnectionStrings:Database", _database.GetConnectionString()), - new KeyValuePair("JWT:Authority", "https://localhost"), - new KeyValuePair("JWT:Audience", "https://localhost"), - new KeyValuePair("JWT:Issuer", "https://localhost"), + new KeyValuePair("JWT:ValidAudience", "https://localhost"), + new KeyValuePair("JWT:MetadataUrl", "https://localhost"), new KeyValuePair("JWT:NameClaimType", null), ]; -- 2.49.1 From 81b5c89a2581584cee6141c9a3f320b65cf9e91c Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 17 Aug 2024 16:38:40 +0200 Subject: [PATCH 008/150] Fix Dockerfile --- src/WebApi/Dockerfile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/WebApi/Dockerfile b/src/WebApi/Dockerfile index 25719d5..98c2413 100644 --- a/src/WebApi/Dockerfile +++ b/src/WebApi/Dockerfile @@ -9,17 +9,17 @@ EXPOSE 8081 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["src/Api/Api.csproj", "src/Api/"] -RUN dotnet restore "./src/Api/Api.csproj" -COPY . . -WORKDIR "/src/src/Api" -RUN dotnet build "./Api.csproj" -c $BUILD_CONFIGURATION -o /app/build +COPY ["WebApi.csproj", "src/WebApi/"] +RUN dotnet restore "./src/WebApi/WebApi.csproj" +COPY . src/WebApi +WORKDIR "/src/src/WebApi" +RUN dotnet build "./WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/build FROM build AS publish ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "./Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false +RUN dotnet publish "./WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", "Api.dll"] \ No newline at end of file +ENTRYPOINT ["dotnet", "WebApi.dll"] \ No newline at end of file -- 2.49.1 From 7f734aa2a27978367f3ad143733590ea3818d823 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 17 Aug 2024 16:38:41 +0200 Subject: [PATCH 009/150] Initial setup work for system tests --- tests/WebApi.Tests.System/Dockerfile.keycloak | 8 + .../WebApi.Tests.System.csproj | 23 + tests/WebApi.Tests.System/compose.system.yaml | 69 + tests/WebApi.Tests.System/test-realm.json | 2332 +++++++++++++++++ vegasco-server.sln | 9 +- 5 files changed, 2440 insertions(+), 1 deletion(-) create mode 100644 tests/WebApi.Tests.System/Dockerfile.keycloak create mode 100644 tests/WebApi.Tests.System/WebApi.Tests.System.csproj create mode 100644 tests/WebApi.Tests.System/compose.system.yaml create mode 100644 tests/WebApi.Tests.System/test-realm.json diff --git a/tests/WebApi.Tests.System/Dockerfile.keycloak b/tests/WebApi.Tests.System/Dockerfile.keycloak new file mode 100644 index 0000000..c71c02f --- /dev/null +++ b/tests/WebApi.Tests.System/Dockerfile.keycloak @@ -0,0 +1,8 @@ +FROM registry.access.redhat.com/ubi9 AS ubi-micro-build +RUN mkdir -p /mnt/rootfs +RUN dnf install --installroot /mnt/rootfs curl --releasever 9 --setopt install_weak_deps=false --nodocs -y && \ + dnf --installroot /mnt/rootfs clean all && \ + rpm --root /mnt/rootfs -e --nodeps setup + +FROM quay.io/keycloak/keycloak +COPY --from=ubi-micro-build /mnt/rootfs / \ No newline at end of file diff --git a/tests/WebApi.Tests.System/WebApi.Tests.System.csproj b/tests/WebApi.Tests.System/WebApi.Tests.System.csproj new file mode 100644 index 0000000..9c5b30a --- /dev/null +++ b/tests/WebApi.Tests.System/WebApi.Tests.System.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + diff --git a/tests/WebApi.Tests.System/compose.system.yaml b/tests/WebApi.Tests.System/compose.system.yaml new file mode 100644 index 0000000..04750c0 --- /dev/null +++ b/tests/WebApi.Tests.System/compose.system.yaml @@ -0,0 +1,69 @@ +services: + app: + build: ../../src/WebApi + environment: + Vegasco_ConnectionStrings__Default: "Host=db;Port=5432;Database=postgres;Username=postgres;Password=postgres" + Vegasco_JWT__Issuer: + Vegasco_JWT__Authority: + Vegasco_JWT__Audience: + depends_on: + db: + condition: service_healthy + login: + condition: service_healthy + + db: + image: postgres:16.3-alpine + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + healthcheck: + test: pg_isready -d postgres + interval: 5s + timeout: 2s + retries: 3 + start_period: 5s + + login: + build: + context: . + dockerfile: Dockerfile.keycloak + command: start --import-realm + environment: + KC_DB: postgres + KC_DB_URL_HOST: login-db + KC_DB_URL_PORT: 5432 + KC_DB_URL_DATABASE: keycloak + KC_DB_USERNAME: keycloak + KC_DB_PASSWORD: keycloak + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin1! + KC_HOSTNAME: http://localhost:12345/ + KC_HEALTH_ENABLED: true + KC_METRICS_ENABLED: true + KC_HTTP_ENABLED: true + ports: + - 12345:8080 + volumes: + - ./test-realm.json:/opt/keycloak/data/import/realm.json:ro + depends_on: + login-db: + condition: service_healthy + healthcheck: + test: curl --head -fsS http://localhost:9000/health/ready || exit 1 + interval: 5s + timeout: 2s + retries: 6 + start_period: 5s + + login-db: + image: postgres:16-alpine + environment: + POSTGRES_USER: keycloak + POSTGRES_PASSWORD: keycloak + healthcheck: + test: pg_isready -d keycloak + interval: 5s + timeout: 2s + retries: 3 + start_period: 5s diff --git a/tests/WebApi.Tests.System/test-realm.json b/tests/WebApi.Tests.System/test-realm.json new file mode 100644 index 0000000..e487a9a --- /dev/null +++ b/tests/WebApi.Tests.System/test-realm.json @@ -0,0 +1,2332 @@ +{ + "id": "59058b68-b3dd-408a-a0cc-8be9ec080347", + "realm": "development", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxTemporaryLockouts": 0, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "99223865-08d1-446f-986d-2fb8cff0730b", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "59058b68-b3dd-408a-a0cc-8be9ec080347", + "attributes": {} + }, + { + "id": "f9f1c2d3-dd92-4527-82d1-5d4a9b2ced66", + "name": "default-roles-development", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "view-profile", + "manage-account" + ] + } + }, + "clientRole": false, + "containerId": "59058b68-b3dd-408a-a0cc-8be9ec080347", + "attributes": {} + }, + { + "id": "8986fd0e-4c10-4fb7-a8f6-83e408c29e95", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "59058b68-b3dd-408a-a0cc-8be9ec080347", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "5964f7e7-3702-4499-b3f6-7aa1d3e80f11", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "4f2daa75-e42b-41bb-aa10-22aa64936a93", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "create-client", + "manage-authorization", + "manage-users", + "view-users", + "view-authorization", + "manage-identity-providers", + "impersonation", + "manage-realm", + "view-realm", + "query-users", + "manage-events", + "query-clients", + "query-groups", + "manage-clients", + "view-clients", + "view-identity-providers", + "query-realms", + "view-events" + ] + } + }, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "f64e7de4-fc10-491c-8f7f-817b70c53cbc", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "64bdb656-58fc-4059-83f5-6af88e0d2d94", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "9acf79b8-6026-426a-b788-078833c96046", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "00172343-72d7-46f8-b1bb-30c98d062335", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-users", + "query-groups" + ] + } + }, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "aaf57855-d18e-4ebf-a89f-8479d6584c78", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "843a9a47-a4d6-46b7-bd15-56134419df36", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "83d6dabe-adf4-4921-b84f-0cb6d67c8524", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "4f8fbeb9-2150-48d3-9ff0-fadb9f952dec", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "da835590-2ff2-47a3-8eda-9ddd27f1f55e", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "9dd8eac9-f0b8-47eb-aa16-fdbcb716ffa8", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "7fdafa52-5875-4f1c-bc2f-bcfe56214329", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "9d4ecec1-6b73-42dc-a7ec-fc9b89e35cec", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "125ca24f-fde8-4340-9c59-ff8b605a89e6", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "7a45ba95-b3e3-4b84-a1d4-1b725a182667", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "ecf9933f-de72-4917-8553-7e18332d217d", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "c51ed824-8455-4584-883e-135d9af5ee4b", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + }, + { + "id": "5c72a2e9-bc45-4ae0-a5cf-d09e70c1b61c", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes": {} + } + ], + "vegasco": [], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "386764da-c0c3-46c8-ae18-518eff6b6b84", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "1f8df246-a2f8-4f21-8c20-eda7dcdae2b6", + "attributes": {} + } + ], + "account": [ + { + "id": "c6e146aa-1a5c-4fcc-9a65-7033c5ec1c95", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes": {} + }, + { + "id": "3141a4ac-31b6-4eb4-9b13-29ea1317b721", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes": {} + }, + { + "id": "7f7e8345-2e96-40af-94c8-f1b4e3d0314a", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes": {} + }, + { + "id": "18e3cd14-4705-4f5d-ab70-c368cab6434e", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes": {} + }, + { + "id": "68a0330a-0c0a-480c-80b4-2d7a11905741", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes": {} + }, + { + "id": "7ee101f9-fab5-4cf2-ab0c-f8d8b2eea394", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes": {} + }, + { + "id": "c0b24a71-4f3c-4ed1-9eee-84e3cbba9adb", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes": {} + }, + { + "id": "e06cb945-b1d9-4013-ba54-c5e72ad08d65", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "f9f1c2d3-dd92-4527-82d1-5d4a9b2ced66", + "name": "default-roles-development", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "59058b68-b3dd-408a-a0cc-8be9ec080347" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppFreeOTPName", + "totpAppGoogleName", + "totpAppMicrosoftAuthenticatorName" + ], + "localizationTexts": {}, + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyExtraOrigins": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "webAuthnPolicyPasswordlessExtraOrigins": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] + }, + "clients": [ + { + "id": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/development/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/development/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "422b7172-a668-43e2-a00b-3f153793e4a1", + "name": "docker-v2-allow-all-mapper", + "protocol": "docker-v2", + "protocolMapper": "docker-v2-allow-all-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "7c4aaabb-f092-4ace-9bc8-6f728336cf26", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/development/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/development/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "a6215df7-0c5e-4347-ba79-4df4fb588b06", + "name": "docker-v2-allow-all-mapper", + "protocol": "docker-v2", + "protocolMapper": "docker-v2-allow-all-mapper", + "consentRequired": false, + "config": {} + }, + { + "id": "5a0023ed-d354-4c0d-b8c6-a3eeada27299", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "2cbd4c50-560a-4a30-8dd8-ce69000ad431", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "a3476370-00d3-44f5-882f-6bf8cdcf64c5", + "name": "docker-v2-allow-all-mapper", + "protocol": "docker-v2", + "protocolMapper": "docker-v2-allow-all-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "1f8df246-a2f8-4f21-8c20-eda7dcdae2b6", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "ad293b7e-096d-48a0-9ac9-27e357f50bdb", + "name": "docker-v2-allow-all-mapper", + "protocol": "docker-v2", + "protocolMapper": "docker-v2-allow-all-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "beea0490-5673-465b-8bd9-2bb7dd546429", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "de0cbd5d-29f7-49e8-8f72-dccbea133782", + "name": "docker-v2-allow-all-mapper", + "protocol": "docker-v2", + "protocolMapper": "docker-v2-allow-all-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "c8e21e7a-7616-4309-9061-793ffda8936a", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/development/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/development/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "f2f24f2c-7f00-4c1b-862c-a1d821965330", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "14db6a94-fd3f-40b2-95a2-139e329e51cf", + "name": "docker-v2-allow-all-mapper", + "protocol": "docker-v2", + "protocolMapper": "docker-v2-allow-all-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "d6877a14-f114-453c-a88c-dbe2472e4ed8", + "clientId": "vegasco", + "name": "Vegasco", + "description": "", + "rootUrl": "http://localhost/", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "*", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "514219d4-0807-44c2-90e8-310634357c0e", + "name": "Vegasco_Audience_Mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "consentRequired": false, + "config": { + "included.client.audience": "vegasco", + "id.token.claim": "false", + "lightweight.claim": "false", + "access.token.claim": "true", + "introspection.token.claim": "true" + } + }, + { + "id": "de5204df-ee72-4105-9640-cc01ddf08b18", + "name": "docker-v2-allow-all-mapper", + "protocol": "docker-v2", + "protocolMapper": "docker-v2-allow-all-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "029c8cc4-a0b9-4c90-9f5f-63a408c7ee49", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "daf886da-43dc-424d-a089-bb564085b128", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${rolesScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "485ebff8-7f64-444a-aa59-446ab3e02c20", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "access.token.claim": "true", + "introspection.token.claim": "true" + } + }, + { + "id": "7e276de2-892a-457e-8437-0fa8d9029549", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "35b0b4fb-9f6d-4695-9cf6-c2fc99581d4c", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "167dfb37-d340-4fe9-ae50-6e7cbaac4f31", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "09fc2f1e-8c67-44f8-b9fd-a837db569332", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "introspection.token.claim": "true" + } + } + ] + }, + { + "id": "c79fc776-2b2d-43d6-b12c-f57262311b6a", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${profileScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "2cd6cd74-4a6a-4221-adc6-804b7227e1e5", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "0f7d36bb-7540-4063-8dcc-26c6408f6d94", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "ce756133-7f49-43e9-8b32-ca358eddf877", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "1729b872-9632-4f52-8726-8d5a8e77d2ca", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "4db87e96-0bfd-4f05-91d1-ca675db6f74e", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "0c589f3d-02ae-4584-b5d2-95c18c2c93be", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "985d5281-c952-47df-8ed4-6e34ba87e518", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "cd7d78c6-7eaa-4b92-8d70-d077d4bcee6c", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "5a0fd3a1-ab3a-4428-bb7c-de529a1021dc", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "7303fd26-77bc-45d6-8810-27e22706d3ff", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "1b9ef4aa-bd49-444c-9ba0-aa2178237beb", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "aba2eec9-c04b-453a-95b7-001700e91eed", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "cd17207d-9f14-4c36-991d-55eb4ef2825e", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "4aa38a97-aaaf-49bc-9f50-4b6c85bf8051", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "90531319-c512-4266-9a2c-39ca2d6c4d19", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${emailScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "3e138a1b-7dbf-4ac2-a8b4-300a0951ccb3", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "e50753c5-bfc5-400f-87b8-be7be8c23a87", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "26fc7ca8-3561-4b0f-a79e-e983ffac13c0", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${phoneScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "5afe3ea6-523f-4619-bafa-02c8516af419", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "7ce91fbe-4392-4126-a0df-18a6fb19c461", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "bfc650b4-2065-47df-807d-bd0efdb59a84", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "ebafee7b-5762-45ff-8054-fad86428a70e", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": { + "access.token.claim": "true", + "introspection.token.claim": "true" + } + } + ] + }, + { + "id": "1ef07288-a201-466e-ad80-a1160ec4d84c", + "name": "basic", + "description": "OpenID Connect scope for add all basic claims to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "264e35b8-e5c6-4607-a412-a4b021ade86d", + "name": "auth_time", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "AUTH_TIME", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "auth_time", + "jsonType.label": "long" + } + }, + { + "id": "e2d85430-984e-4933-ab8a-095018599676", + "name": "sub", + "protocol": "openid-connect", + "protocolMapper": "oidc-sub-mapper", + "consentRequired": false, + "config": { + "access.token.claim": "true", + "introspection.token.claim": "true" + } + } + ] + }, + { + "id": "c2825d61-d98b-4200-9b7c-699635e4822e", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "02aef283-5cf6-4a5b-8879-0973efd8dd01", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "8938588f-e269-49c2-ad52-afe54224cdaf", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "bfee5f0c-7b97-4569-bf06-942e6865e14c", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "09dddeb4-9b2f-45b4-8c60-3c1d62e64d75", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "773e90cb-bad8-438b-98c5-0174c799a37a", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${addressScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "94842824-7c55-4576-ab27-e03079620f39", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "introspection.token.claim": "true", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr", + "basic" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "referrerPolicy": "no-referrer", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "87c77663-33a5-41fa-805e-ad6e16ac693a", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "93b727d6-92be-4ae7-9436-cd8a67e13576", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", + "oidc-address-mapper", + "saml-role-list-mapper", + "oidc-usermodel-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-full-name-mapper", + "saml-user-attribute-mapper" + ] + } + }, + { + "id": "b099d087-5954-460d-902f-def7799cb005", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "16f67ef8-1431-4241-a7a3-0849513dd422", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-full-name-mapper", + "saml-user-attribute-mapper", + "oidc-address-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-usermodel-property-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper" + ] + } + }, + { + "id": "e86226d6-0944-4c08-b809-d72e6c7991c4", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "c796a096-9c20-4983-b7bc-cb282936040f", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "76051f4c-b020-449d-a40f-664f918d0082", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "055d0fd2-9546-466d-b945-914f5ce84272", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "bb2e382d-8ed7-41ab-b545-defd7cf035dd", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "ca7e3c60-4007-4fbd-bb58-80989d4ef95f", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "5ddf1462-4890-492a-919d-5a4cf34d8e74", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "64782b75-6f52-4f1d-af06-09ad6bfcdfb9", + "name": "hmac-generated-hs512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS512" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "17180a8f-635e-42e3-9870-88b7b61fb915", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "c416b7a7-45c8-4f68-99b9-0fd7d817fe18", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "7d9a6e6e-6a2e-4453-aac4-fd2e0f158f4b", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "5a9053a6-432c-4380-b267-96f86cfac2a7", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "f12fa4ea-704b-4f25-8a50-2b6c011d0249", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "1fe6bf4e-f2f8-4631-b024-6e8a5bd8c42e", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "6246dbfb-a619-4635-ab3e-b394f0ed4af3", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "efa63a87-ea5c-4519-a323-11099438f81e", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "2cd74a88-7aff-42ad-a94e-776089c0aaca", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "f14b6e2e-bf4f-4dc3-906e-8e417cb8a96b", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "704eac4a-0f3f-42b1-8326-ed8193f7c5e0", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "ea358460-4bdb-4926-8bdb-9ae28b76a73a", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "064b83a9-eff4-47c0-a885-5a2552a390b7", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "224e3374-9c81-437a-9abb-557eba90dcea", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "ccfe54ac-0522-45a5-8cf5-a3bd89396ea8", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "09319bf1-dc33-4d5a-9866-20ef01e3364e", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-terms-and-conditions", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 70, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "e55eed94-e227-4edc-af34-5d8595fb2379", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "4ce089cb-31f2-4c4c-99a8-fd7f2c6b458e", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "ca32fd2e-b807-49d6-aa2a-ece90de7c6d1", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "87fbbfb1-9fad-4954-85be-84d15d6f8bc6", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "TERMS_AND_CONDITIONS", + "name": "Terms and Conditions", + "providerId": "TERMS_AND_CONDITIONS", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "VERIFY_PROFILE", + "name": "Verify Profile", + "providerId": "VERIFY_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 90, + "config": {} + }, + { + "alias": "delete_credential", + "name": "Delete Credential", + "providerId": "delete_credential", + "enabled": true, + "defaultAction": false, + "priority": 100, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "firstBrokerLoginFlow": "first broker login", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "parRequestUriLifespan": "60", + "cibaInterval": "5", + "realmReusableOtpCode": "false" + }, + "keycloakVersion": "25.0.2", + "userManagedAccessAllowed": false, + "organizationsEnabled": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} \ No newline at end of file diff --git a/vegasco-server.sln b/vegasco-server.sln index 7c09892..3f6457e 100644 --- a/vegasco-server.sln +++ b/vegasco-server.sln @@ -16,7 +16,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{437DE053 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests.Unit", "tests\WebApi.Tests.Unit\WebApi.Tests.Unit.csproj", "{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.Integration", "tests\WebApi.Tests.Integration\WebApi.Tests.Integration.csproj", "{0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests.Integration", "tests\WebApi.Tests.Integration\WebApi.Tests.Integration.csproj", "{0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.System", "tests\WebApi.Tests.System\WebApi.Tests.System.csproj", "{21418359-4A20-4F4A-B26C-A75A2B70AA10}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -36,6 +38,10 @@ Global {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Debug|Any CPU.Build.0 = Debug|Any CPU {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Release|Any CPU.ActiveCfg = Release|Any CPU {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Release|Any CPU.Build.0 = Release|Any CPU + {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -44,6 +50,7 @@ Global {9FF3C98A-5085-4EBE-A980-DB2148B0C00A} = {C051A684-BD6A-43F2-B0CC-F3C2315D99E3} {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} + {21418359-4A20-4F4A-B26C-A75A2B70AA10} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7813E32D-AE19-479C-853B-063882D2D05A} -- 2.49.1 From 4f287d85ddf5b831fe5e8ebb40bc7a8b76b180bf Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 17 Aug 2024 16:38:41 +0200 Subject: [PATCH 010/150] Use full keycloak realm export from cli instead of partial export from web interface --- tests/WebApi.Tests.System/compose.system.yaml | 2 +- tests/WebApi.Tests.System/test-realm.json | 4227 ++++++++--------- 2 files changed, 1927 insertions(+), 2302 deletions(-) diff --git a/tests/WebApi.Tests.System/compose.system.yaml b/tests/WebApi.Tests.System/compose.system.yaml index 04750c0..4df77c0 100644 --- a/tests/WebApi.Tests.System/compose.system.yaml +++ b/tests/WebApi.Tests.System/compose.system.yaml @@ -45,7 +45,7 @@ services: ports: - 12345:8080 volumes: - - ./test-realm.json:/opt/keycloak/data/import/realm.json:ro + - ./test-realm.json:/opt/keycloak/data/import/test-realm.json:ro depends_on: login-db: condition: service_healthy diff --git a/tests/WebApi.Tests.System/test-realm.json b/tests/WebApi.Tests.System/test-realm.json index e487a9a..d654156 100644 --- a/tests/WebApi.Tests.System/test-realm.json +++ b/tests/WebApi.Tests.System/test-realm.json @@ -1,2332 +1,1957 @@ { - "id": "59058b68-b3dd-408a-a0cc-8be9ec080347", - "realm": "development", - "notBefore": 0, - "defaultSignatureAlgorithm": "RS256", - "revokeRefreshToken": false, - "refreshTokenMaxReuse": 0, - "accessTokenLifespan": 300, - "accessTokenLifespanForImplicitFlow": 900, - "ssoSessionIdleTimeout": 1800, - "ssoSessionMaxLifespan": 36000, - "ssoSessionIdleTimeoutRememberMe": 0, - "ssoSessionMaxLifespanRememberMe": 0, - "offlineSessionIdleTimeout": 2592000, - "offlineSessionMaxLifespanEnabled": false, - "offlineSessionMaxLifespan": 5184000, - "clientSessionIdleTimeout": 0, - "clientSessionMaxLifespan": 0, - "clientOfflineSessionIdleTimeout": 0, - "clientOfflineSessionMaxLifespan": 0, - "accessCodeLifespan": 60, - "accessCodeLifespanUserAction": 300, - "accessCodeLifespanLogin": 1800, - "actionTokenGeneratedByAdminLifespan": 43200, - "actionTokenGeneratedByUserLifespan": 300, - "oauth2DeviceCodeLifespan": 600, - "oauth2DevicePollingInterval": 5, - "enabled": true, - "sslRequired": "external", - "registrationAllowed": false, - "registrationEmailAsUsername": false, - "rememberMe": false, - "verifyEmail": false, - "loginWithEmailAllowed": true, - "duplicateEmailsAllowed": false, - "resetPasswordAllowed": false, - "editUsernameAllowed": false, - "bruteForceProtected": false, - "permanentLockout": false, - "maxTemporaryLockouts": 0, - "maxFailureWaitSeconds": 900, - "minimumQuickLoginWaitSeconds": 60, - "waitIncrementSeconds": 60, - "quickLoginCheckMilliSeconds": 1000, - "maxDeltaTimeSeconds": 43200, - "failureFactor": 30, - "roles": { - "realm": [ - { - "id": "99223865-08d1-446f-986d-2fb8cff0730b", - "name": "offline_access", - "description": "${role_offline-access}", - "composite": false, - "clientRole": false, - "containerId": "59058b68-b3dd-408a-a0cc-8be9ec080347", - "attributes": {} + "id" : "59058b68-b3dd-408a-a0cc-8be9ec080347", + "realm" : "development", + "notBefore" : 0, + "defaultSignatureAlgorithm" : "RS256", + "revokeRefreshToken" : false, + "refreshTokenMaxReuse" : 0, + "accessTokenLifespan" : 300, + "accessTokenLifespanForImplicitFlow" : 900, + "ssoSessionIdleTimeout" : 1800, + "ssoSessionMaxLifespan" : 36000, + "ssoSessionIdleTimeoutRememberMe" : 0, + "ssoSessionMaxLifespanRememberMe" : 0, + "offlineSessionIdleTimeout" : 2592000, + "offlineSessionMaxLifespanEnabled" : false, + "offlineSessionMaxLifespan" : 5184000, + "clientSessionIdleTimeout" : 0, + "clientSessionMaxLifespan" : 0, + "clientOfflineSessionIdleTimeout" : 0, + "clientOfflineSessionMaxLifespan" : 0, + "accessCodeLifespan" : 60, + "accessCodeLifespanUserAction" : 300, + "accessCodeLifespanLogin" : 1800, + "actionTokenGeneratedByAdminLifespan" : 43200, + "actionTokenGeneratedByUserLifespan" : 300, + "oauth2DeviceCodeLifespan" : 600, + "oauth2DevicePollingInterval" : 5, + "enabled" : true, + "sslRequired" : "external", + "registrationAllowed" : false, + "registrationEmailAsUsername" : false, + "rememberMe" : false, + "verifyEmail" : false, + "loginWithEmailAllowed" : true, + "duplicateEmailsAllowed" : false, + "resetPasswordAllowed" : false, + "editUsernameAllowed" : false, + "bruteForceProtected" : false, + "permanentLockout" : false, + "maxTemporaryLockouts" : 0, + "maxFailureWaitSeconds" : 900, + "minimumQuickLoginWaitSeconds" : 60, + "waitIncrementSeconds" : 60, + "quickLoginCheckMilliSeconds" : 1000, + "maxDeltaTimeSeconds" : 43200, + "failureFactor" : 30, + "roles" : { + "realm" : [ { + "id" : "99223865-08d1-446f-986d-2fb8cff0730b", + "name" : "offline_access", + "description" : "${role_offline-access}", + "composite" : false, + "clientRole" : false, + "containerId" : "59058b68-b3dd-408a-a0cc-8be9ec080347", + "attributes" : { } + }, { + "id" : "f9f1c2d3-dd92-4527-82d1-5d4a9b2ced66", + "name" : "default-roles-development", + "description" : "${role_default-roles}", + "composite" : true, + "composites" : { + "realm" : [ "offline_access", "uma_authorization" ], + "client" : { + "account" : [ "view-profile", "manage-account" ] + } }, - { - "id": "f9f1c2d3-dd92-4527-82d1-5d4a9b2ced66", - "name": "default-roles-development", - "description": "${role_default-roles}", - "composite": true, - "composites": { - "realm": [ - "offline_access", - "uma_authorization" - ], - "client": { - "account": [ - "view-profile", - "manage-account" - ] + "clientRole" : false, + "containerId" : "59058b68-b3dd-408a-a0cc-8be9ec080347", + "attributes" : { } + }, { + "id" : "8986fd0e-4c10-4fb7-a8f6-83e408c29e95", + "name" : "uma_authorization", + "description" : "${role_uma_authorization}", + "composite" : false, + "clientRole" : false, + "containerId" : "59058b68-b3dd-408a-a0cc-8be9ec080347", + "attributes" : { } + } ], + "client" : { + "realm-management" : [ { + "id" : "5964f7e7-3702-4499-b3f6-7aa1d3e80f11", + "name" : "create-client", + "description" : "${role_create-client}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "4f2daa75-e42b-41bb-aa10-22aa64936a93", + "name" : "realm-admin", + "description" : "${role_realm-admin}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "create-client", "manage-authorization", "manage-users", "view-authorization", "view-users", "manage-identity-providers", "impersonation", "manage-realm", "view-realm", "query-users", "manage-events", "query-clients", "query-groups", "manage-clients", "view-clients", "view-identity-providers", "query-realms", "view-events" ] } }, - "clientRole": false, - "containerId": "59058b68-b3dd-408a-a0cc-8be9ec080347", - "attributes": {} - }, - { - "id": "8986fd0e-4c10-4fb7-a8f6-83e408c29e95", - "name": "uma_authorization", - "description": "${role_uma_authorization}", - "composite": false, - "clientRole": false, - "containerId": "59058b68-b3dd-408a-a0cc-8be9ec080347", - "attributes": {} - } - ], - "client": { - "realm-management": [ - { - "id": "5964f7e7-3702-4499-b3f6-7aa1d3e80f11", - "name": "create-client", - "description": "${role_create-client}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "f64e7de4-fc10-491c-8f7f-817b70c53cbc", + "name" : "manage-authorization", + "description" : "${role_manage-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "64bdb656-58fc-4059-83f5-6af88e0d2d94", + "name" : "manage-users", + "description" : "${role_manage-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "9acf79b8-6026-426a-b788-078833c96046", + "name" : "view-authorization", + "description" : "${role_view-authorization}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "00172343-72d7-46f8-b1bb-30c98d062335", + "name" : "view-users", + "description" : "${role_view-users}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-users", "query-groups" ] + } }, - { - "id": "4f2daa75-e42b-41bb-aa10-22aa64936a93", - "name": "realm-admin", - "description": "${role_realm-admin}", - "composite": true, - "composites": { - "client": { - "realm-management": [ - "create-client", - "manage-authorization", - "manage-users", - "view-users", - "view-authorization", - "manage-identity-providers", - "impersonation", - "manage-realm", - "view-realm", - "query-users", - "manage-events", - "query-clients", - "query-groups", - "manage-clients", - "view-clients", - "view-identity-providers", - "query-realms", - "view-events" - ] - } - }, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "aaf57855-d18e-4ebf-a89f-8479d6584c78", + "name" : "manage-identity-providers", + "description" : "${role_manage-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "843a9a47-a4d6-46b7-bd15-56134419df36", + "name" : "impersonation", + "description" : "${role_impersonation}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "83d6dabe-adf4-4921-b84f-0cb6d67c8524", + "name" : "manage-realm", + "description" : "${role_manage-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "4f8fbeb9-2150-48d3-9ff0-fadb9f952dec", + "name" : "view-realm", + "description" : "${role_view-realm}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "da835590-2ff2-47a3-8eda-9ddd27f1f55e", + "name" : "query-users", + "description" : "${role_query-users}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "9dd8eac9-f0b8-47eb-aa16-fdbcb716ffa8", + "name" : "manage-events", + "description" : "${role_manage-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "7fdafa52-5875-4f1c-bc2f-bcfe56214329", + "name" : "query-clients", + "description" : "${role_query-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "9d4ecec1-6b73-42dc-a7ec-fc9b89e35cec", + "name" : "query-groups", + "description" : "${role_query-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "125ca24f-fde8-4340-9c59-ff8b605a89e6", + "name" : "manage-clients", + "description" : "${role_manage-clients}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "7a45ba95-b3e3-4b84-a1d4-1b725a182667", + "name" : "view-clients", + "description" : "${role_view-clients}", + "composite" : true, + "composites" : { + "client" : { + "realm-management" : [ "query-clients" ] + } }, - { - "id": "f64e7de4-fc10-491c-8f7f-817b70c53cbc", - "name": "manage-authorization", - "description": "${role_manage-authorization}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "ecf9933f-de72-4917-8553-7e18332d217d", + "name" : "view-identity-providers", + "description" : "${role_view-identity-providers}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "c51ed824-8455-4584-883e-135d9af5ee4b", + "name" : "query-realms", + "description" : "${role_query-realms}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + }, { + "id" : "5c72a2e9-bc45-4ae0-a5cf-d09e70c1b61c", + "name" : "view-events", + "description" : "${role_view-events}", + "composite" : false, + "clientRole" : true, + "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "attributes" : { } + } ], + "vegasco" : [ ], + "security-admin-console" : [ ], + "admin-cli" : [ ], + "account-console" : [ ], + "broker" : [ { + "id" : "386764da-c0c3-46c8-ae18-518eff6b6b84", + "name" : "read-token", + "description" : "${role_read-token}", + "composite" : false, + "clientRole" : true, + "containerId" : "1f8df246-a2f8-4f21-8c20-eda7dcdae2b6", + "attributes" : { } + } ], + "account" : [ { + "id" : "c6e146aa-1a5c-4fcc-9a65-7033c5ec1c95", + "name" : "manage-account-links", + "description" : "${role_manage-account-links}", + "composite" : false, + "clientRole" : true, + "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes" : { } + }, { + "id" : "3141a4ac-31b6-4eb4-9b13-29ea1317b721", + "name" : "manage-consent", + "description" : "${role_manage-consent}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "view-consent" ] + } }, - { - "id": "64bdb656-58fc-4059-83f5-6af88e0d2d94", - "name": "manage-users", - "description": "${role_manage-users}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} + "clientRole" : true, + "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes" : { } + }, { + "id" : "7f7e8345-2e96-40af-94c8-f1b4e3d0314a", + "name" : "view-profile", + "description" : "${role_view-profile}", + "composite" : false, + "clientRole" : true, + "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes" : { } + }, { + "id" : "18e3cd14-4705-4f5d-ab70-c368cab6434e", + "name" : "delete-account", + "description" : "${role_delete-account}", + "composite" : false, + "clientRole" : true, + "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes" : { } + }, { + "id" : "68a0330a-0c0a-480c-80b4-2d7a11905741", + "name" : "view-groups", + "description" : "${role_view-groups}", + "composite" : false, + "clientRole" : true, + "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes" : { } + }, { + "id" : "7ee101f9-fab5-4cf2-ab0c-f8d8b2eea394", + "name" : "manage-account", + "description" : "${role_manage-account}", + "composite" : true, + "composites" : { + "client" : { + "account" : [ "manage-account-links" ] + } }, - { - "id": "9acf79b8-6026-426a-b788-078833c96046", - "name": "view-authorization", - "description": "${role_view-authorization}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "00172343-72d7-46f8-b1bb-30c98d062335", - "name": "view-users", - "description": "${role_view-users}", - "composite": true, - "composites": { - "client": { - "realm-management": [ - "query-users", - "query-groups" - ] - } - }, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "aaf57855-d18e-4ebf-a89f-8479d6584c78", - "name": "manage-identity-providers", - "description": "${role_manage-identity-providers}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "843a9a47-a4d6-46b7-bd15-56134419df36", - "name": "impersonation", - "description": "${role_impersonation}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "83d6dabe-adf4-4921-b84f-0cb6d67c8524", - "name": "manage-realm", - "description": "${role_manage-realm}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "4f8fbeb9-2150-48d3-9ff0-fadb9f952dec", - "name": "view-realm", - "description": "${role_view-realm}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "da835590-2ff2-47a3-8eda-9ddd27f1f55e", - "name": "query-users", - "description": "${role_query-users}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "9dd8eac9-f0b8-47eb-aa16-fdbcb716ffa8", - "name": "manage-events", - "description": "${role_manage-events}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "7fdafa52-5875-4f1c-bc2f-bcfe56214329", - "name": "query-clients", - "description": "${role_query-clients}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "9d4ecec1-6b73-42dc-a7ec-fc9b89e35cec", - "name": "query-groups", - "description": "${role_query-groups}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "125ca24f-fde8-4340-9c59-ff8b605a89e6", - "name": "manage-clients", - "description": "${role_manage-clients}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "7a45ba95-b3e3-4b84-a1d4-1b725a182667", - "name": "view-clients", - "description": "${role_view-clients}", - "composite": true, - "composites": { - "client": { - "realm-management": [ - "query-clients" - ] - } - }, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "ecf9933f-de72-4917-8553-7e18332d217d", - "name": "view-identity-providers", - "description": "${role_view-identity-providers}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "c51ed824-8455-4584-883e-135d9af5ee4b", - "name": "query-realms", - "description": "${role_query-realms}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - }, - { - "id": "5c72a2e9-bc45-4ae0-a5cf-d09e70c1b61c", - "name": "view-events", - "description": "${role_view-events}", - "composite": false, - "clientRole": true, - "containerId": "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes": {} - } - ], - "vegasco": [], - "security-admin-console": [], - "admin-cli": [], - "account-console": [], - "broker": [ - { - "id": "386764da-c0c3-46c8-ae18-518eff6b6b84", - "name": "read-token", - "description": "${role_read-token}", - "composite": false, - "clientRole": true, - "containerId": "1f8df246-a2f8-4f21-8c20-eda7dcdae2b6", - "attributes": {} - } - ], - "account": [ - { - "id": "c6e146aa-1a5c-4fcc-9a65-7033c5ec1c95", - "name": "manage-account-links", - "description": "${role_manage-account-links}", - "composite": false, - "clientRole": true, - "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes": {} - }, - { - "id": "3141a4ac-31b6-4eb4-9b13-29ea1317b721", - "name": "manage-consent", - "description": "${role_manage-consent}", - "composite": true, - "composites": { - "client": { - "account": [ - "view-consent" - ] - } - }, - "clientRole": true, - "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes": {} - }, - { - "id": "7f7e8345-2e96-40af-94c8-f1b4e3d0314a", - "name": "view-profile", - "description": "${role_view-profile}", - "composite": false, - "clientRole": true, - "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes": {} - }, - { - "id": "18e3cd14-4705-4f5d-ab70-c368cab6434e", - "name": "delete-account", - "description": "${role_delete-account}", - "composite": false, - "clientRole": true, - "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes": {} - }, - { - "id": "68a0330a-0c0a-480c-80b4-2d7a11905741", - "name": "view-groups", - "description": "${role_view-groups}", - "composite": false, - "clientRole": true, - "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes": {} - }, - { - "id": "7ee101f9-fab5-4cf2-ab0c-f8d8b2eea394", - "name": "manage-account", - "description": "${role_manage-account}", - "composite": true, - "composites": { - "client": { - "account": [ - "manage-account-links" - ] - } - }, - "clientRole": true, - "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes": {} - }, - { - "id": "c0b24a71-4f3c-4ed1-9eee-84e3cbba9adb", - "name": "view-consent", - "description": "${role_view-consent}", - "composite": false, - "clientRole": true, - "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes": {} - }, - { - "id": "e06cb945-b1d9-4013-ba54-c5e72ad08d65", - "name": "view-applications", - "description": "${role_view-applications}", - "composite": false, - "clientRole": true, - "containerId": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes": {} - } - ] + "clientRole" : true, + "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes" : { } + }, { + "id" : "c0b24a71-4f3c-4ed1-9eee-84e3cbba9adb", + "name" : "view-consent", + "description" : "${role_view-consent}", + "composite" : false, + "clientRole" : true, + "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes" : { } + }, { + "id" : "e06cb945-b1d9-4013-ba54-c5e72ad08d65", + "name" : "view-applications", + "description" : "${role_view-applications}", + "composite" : false, + "clientRole" : true, + "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "attributes" : { } + } ] } }, - "groups": [], - "defaultRole": { - "id": "f9f1c2d3-dd92-4527-82d1-5d4a9b2ced66", - "name": "default-roles-development", - "description": "${role_default-roles}", - "composite": true, - "clientRole": false, - "containerId": "59058b68-b3dd-408a-a0cc-8be9ec080347" + "groups" : [ ], + "defaultRole" : { + "id" : "f9f1c2d3-dd92-4527-82d1-5d4a9b2ced66", + "name" : "default-roles-development", + "description" : "${role_default-roles}", + "composite" : true, + "clientRole" : false, + "containerId" : "59058b68-b3dd-408a-a0cc-8be9ec080347" }, - "requiredCredentials": [ - "password" - ], - "otpPolicyType": "totp", - "otpPolicyAlgorithm": "HmacSHA1", - "otpPolicyInitialCounter": 0, - "otpPolicyDigits": 6, - "otpPolicyLookAheadWindow": 1, - "otpPolicyPeriod": 30, - "otpPolicyCodeReusable": false, - "otpSupportedApplications": [ - "totpAppFreeOTPName", - "totpAppGoogleName", - "totpAppMicrosoftAuthenticatorName" - ], - "localizationTexts": {}, - "webAuthnPolicyRpEntityName": "keycloak", - "webAuthnPolicySignatureAlgorithms": [ - "ES256" - ], - "webAuthnPolicyRpId": "", - "webAuthnPolicyAttestationConveyancePreference": "not specified", - "webAuthnPolicyAuthenticatorAttachment": "not specified", - "webAuthnPolicyRequireResidentKey": "not specified", - "webAuthnPolicyUserVerificationRequirement": "not specified", - "webAuthnPolicyCreateTimeout": 0, - "webAuthnPolicyAvoidSameAuthenticatorRegister": false, - "webAuthnPolicyAcceptableAaguids": [], - "webAuthnPolicyExtraOrigins": [], - "webAuthnPolicyPasswordlessRpEntityName": "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms": [ - "ES256" - ], - "webAuthnPolicyPasswordlessRpId": "", - "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", - "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", - "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", - "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", - "webAuthnPolicyPasswordlessCreateTimeout": 0, - "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, - "webAuthnPolicyPasswordlessAcceptableAaguids": [], - "webAuthnPolicyPasswordlessExtraOrigins": [], - "scopeMappings": [ - { - "clientScope": "offline_access", - "roles": [ - "offline_access" - ] - } - ], - "clientScopeMappings": { - "account": [ - { - "client": "account-console", - "roles": [ - "manage-account", - "view-groups" - ] - } - ] + "requiredCredentials" : [ "password" ], + "otpPolicyType" : "totp", + "otpPolicyAlgorithm" : "HmacSHA1", + "otpPolicyInitialCounter" : 0, + "otpPolicyDigits" : 6, + "otpPolicyLookAheadWindow" : 1, + "otpPolicyPeriod" : 30, + "otpPolicyCodeReusable" : false, + "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], + "localizationTexts" : { }, + "webAuthnPolicyRpEntityName" : "keycloak", + "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyRpId" : "", + "webAuthnPolicyAttestationConveyancePreference" : "not specified", + "webAuthnPolicyAuthenticatorAttachment" : "not specified", + "webAuthnPolicyRequireResidentKey" : "not specified", + "webAuthnPolicyUserVerificationRequirement" : "not specified", + "webAuthnPolicyCreateTimeout" : 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyAcceptableAaguids" : [ ], + "webAuthnPolicyExtraOrigins" : [ ], + "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], + "webAuthnPolicyPasswordlessRpId" : "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", + "webAuthnPolicyPasswordlessCreateTimeout" : 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, + "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], + "webAuthnPolicyPasswordlessExtraOrigins" : [ ], + "users" : [ { + "id" : "c5404b32-c20e-4af1-859b-18e2df6998a2", + "username" : "test.user", + "firstName" : "test", + "lastName" : "user", + "email" : "test.user@example.com", + "emailVerified" : true, + "createdTimestamp" : 1722885095042, + "enabled" : true, + "totp" : false, + "credentials" : [ { + "id" : "55a67bcf-b7df-4e10-840c-8cedc2e263af", + "type" : "password", + "createdDate" : 1722885095911, + "secretData" : "{\"value\":\"A9/c6FWaGkk7fC9qQmiiH3FlFFpWBjg9ZSvgnJIkd68=\",\"salt\":\"ec93soiRD3MWjohp8XWxfw==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-development" ], + "notBefore" : 0, + "groups" : [ ] + } ], + "scopeMappings" : [ { + "clientScope" : "offline_access", + "roles" : [ "offline_access" ] + } ], + "clientScopeMappings" : { + "account" : [ { + "client" : "account-console", + "roles" : [ "manage-account", "view-groups" ] + } ] }, - "clients": [ - { - "id": "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "clientId": "account", - "name": "${client_account}", - "rootUrl": "${authBaseUrl}", - "baseUrl": "/realms/development/account/", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/realms/development/account/*" - ], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": false, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": true, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": { - "post.logout.redirect.uris": "+" - }, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "protocolMappers": [ - { - "id": "422b7172-a668-43e2-a00b-3f153793e4a1", - "name": "docker-v2-allow-all-mapper", - "protocol": "docker-v2", - "protocolMapper": "docker-v2-allow-all-mapper", - "consentRequired": false, - "config": {} - } - ], - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "basic", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "clients" : [ { + "id" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", + "clientId" : "account", + "name" : "${client_account}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/development/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/development/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" }, - { - "id": "7c4aaabb-f092-4ace-9bc8-6f728336cf26", - "clientId": "account-console", - "name": "${client_account-console}", - "rootUrl": "${authBaseUrl}", - "baseUrl": "/realms/development/account/", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/realms/development/account/*" - ], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": false, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": true, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": { - "post.logout.redirect.uris": "+", - "pkce.code.challenge.method": "S256" - }, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "protocolMappers": [ - { - "id": "a6215df7-0c5e-4347-ba79-4df4fb588b06", - "name": "docker-v2-allow-all-mapper", - "protocol": "docker-v2", - "protocolMapper": "docker-v2-allow-all-mapper", - "consentRequired": false, - "config": {} - }, - { - "id": "5a0023ed-d354-4c0d-b8c6-a3eeada27299", - "name": "audience resolve", - "protocol": "openid-connect", - "protocolMapper": "oidc-audience-resolve-mapper", - "consentRequired": false, - "config": {} - } - ], - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "basic", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "422b7172-a668-43e2-a00b-3f153793e4a1", + "name" : "docker-v2-allow-all-mapper", + "protocol" : "docker-v2", + "protocolMapper" : "docker-v2-allow-all-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "7c4aaabb-f092-4ace-9bc8-6f728336cf26", + "clientId" : "account-console", + "name" : "${client_account-console}", + "rootUrl" : "${authBaseUrl}", + "baseUrl" : "/realms/development/account/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/realms/development/account/*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" }, - { - "id": "2cbd4c50-560a-4a30-8dd8-ce69000ad431", - "clientId": "admin-cli", - "name": "${client_admin-cli}", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "redirectUris": [], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": false, - "consentRequired": false, - "standardFlowEnabled": false, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": true, - "serviceAccountsEnabled": false, - "publicClient": true, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": {}, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "protocolMappers": [ - { - "id": "a3476370-00d3-44f5-882f-6bf8cdcf64c5", - "name": "docker-v2-allow-all-mapper", - "protocol": "docker-v2", - "protocolMapper": "docker-v2-allow-all-mapper", - "consentRequired": false, - "config": {} - } - ], - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "basic", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "a6215df7-0c5e-4347-ba79-4df4fb588b06", + "name" : "docker-v2-allow-all-mapper", + "protocol" : "docker-v2", + "protocolMapper" : "docker-v2-allow-all-mapper", + "consentRequired" : false, + "config" : { } + }, { + "id" : "5a0023ed-d354-4c0d-b8c6-a3eeada27299", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "2cbd4c50-560a-4a30-8dd8-ce69000ad431", + "clientId" : "admin-cli", + "name" : "${client_admin-cli}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : false, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : true, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" }, - { - "id": "1f8df246-a2f8-4f21-8c20-eda7dcdae2b6", - "clientId": "broker", - "name": "${client_broker}", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "redirectUris": [], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": true, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": false, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": {}, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "protocolMappers": [ - { - "id": "ad293b7e-096d-48a0-9ac9-27e357f50bdb", - "name": "docker-v2-allow-all-mapper", - "protocol": "docker-v2", - "protocolMapper": "docker-v2-allow-all-mapper", - "consentRequired": false, - "config": {} - } - ], - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "basic", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "a3476370-00d3-44f5-882f-6bf8cdcf64c5", + "name" : "docker-v2-allow-all-mapper", + "protocol" : "docker-v2", + "protocolMapper" : "docker-v2-allow-all-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "1f8df246-a2f8-4f21-8c20-eda7dcdae2b6", + "clientId" : "broker", + "name" : "${client_broker}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" }, - { - "id": "beea0490-5673-465b-8bd9-2bb7dd546429", - "clientId": "realm-management", - "name": "${client_realm-management}", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "redirectUris": [], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": true, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": false, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": {}, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "protocolMappers": [ - { - "id": "de0cbd5d-29f7-49e8-8f72-dccbea133782", - "name": "docker-v2-allow-all-mapper", - "protocol": "docker-v2", - "protocolMapper": "docker-v2-allow-all-mapper", - "consentRequired": false, - "config": {} - } - ], - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "basic", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "ad293b7e-096d-48a0-9ac9-27e357f50bdb", + "name" : "docker-v2-allow-all-mapper", + "protocol" : "docker-v2", + "protocolMapper" : "docker-v2-allow-all-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "beea0490-5673-465b-8bd9-2bb7dd546429", + "clientId" : "realm-management", + "name" : "${client_realm-management}", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : true, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : false, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+" }, - { - "id": "c8e21e7a-7616-4309-9061-793ffda8936a", - "clientId": "security-admin-console", - "name": "${client_security-admin-console}", - "rootUrl": "${authAdminUrl}", - "baseUrl": "/admin/development/console/", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "/admin/development/console/*" - ], - "webOrigins": [ - "+" - ], - "notBefore": 0, - "bearerOnly": false, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": true, - "frontchannelLogout": false, - "protocol": "openid-connect", - "attributes": { - "post.logout.redirect.uris": "+", - "pkce.code.challenge.method": "S256" - }, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": 0, - "protocolMappers": [ - { - "id": "f2f24f2c-7f00-4c1b-862c-a1d821965330", - "name": "locale", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "locale", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "locale", - "jsonType.label": "String" - } - }, - { - "id": "14db6a94-fd3f-40b2-95a2-139e329e51cf", - "name": "docker-v2-allow-all-mapper", - "protocol": "docker-v2", - "protocolMapper": "docker-v2-allow-all-mapper", - "consentRequired": false, - "config": {} - } - ], - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "basic", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "de0cbd5d-29f7-49e8-8f72-dccbea133782", + "name" : "docker-v2-allow-all-mapper", + "protocol" : "docker-v2", + "protocolMapper" : "docker-v2-allow-all-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "c8e21e7a-7616-4309-9061-793ffda8936a", + "clientId" : "security-admin-console", + "name" : "${client_security-admin-console}", + "rootUrl" : "${authAdminUrl}", + "baseUrl" : "/admin/development/console/", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "/admin/development/console/*" ], + "webOrigins" : [ "+" ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : false, + "protocol" : "openid-connect", + "attributes" : { + "post.logout.redirect.uris" : "+", + "pkce.code.challenge.method" : "S256" }, - { - "id": "d6877a14-f114-453c-a88c-dbe2472e4ed8", - "clientId": "vegasco", - "name": "Vegasco", - "description": "", - "rootUrl": "http://localhost/", - "adminUrl": "", - "baseUrl": "", - "surrogateAuthRequired": false, - "enabled": true, - "alwaysDisplayInConsole": false, - "clientAuthenticatorType": "client-secret", - "redirectUris": [ - "*" - ], - "webOrigins": [], - "notBefore": 0, - "bearerOnly": false, - "consentRequired": false, - "standardFlowEnabled": true, - "implicitFlowEnabled": false, - "directAccessGrantsEnabled": false, - "serviceAccountsEnabled": false, - "publicClient": true, - "frontchannelLogout": true, - "protocol": "openid-connect", - "attributes": { - "oidc.ciba.grant.enabled": "false", - "backchannel.logout.session.required": "true", - "post.logout.redirect.uris": "*", - "oauth2.device.authorization.grant.enabled": "false", - "backchannel.logout.revoke.offline.tokens": "false" - }, - "authenticationFlowBindingOverrides": {}, - "fullScopeAllowed": false, - "nodeReRegistrationTimeout": -1, - "protocolMappers": [ - { - "id": "514219d4-0807-44c2-90e8-310634357c0e", - "name": "Vegasco_Audience_Mapper", - "protocol": "openid-connect", - "protocolMapper": "oidc-audience-mapper", - "consentRequired": false, - "config": { - "included.client.audience": "vegasco", - "id.token.claim": "false", - "lightweight.claim": "false", - "access.token.claim": "true", - "introspection.token.claim": "true" - } - }, - { - "id": "de5204df-ee72-4105-9640-cc01ddf08b18", - "name": "docker-v2-allow-all-mapper", - "protocol": "docker-v2", - "protocolMapper": "docker-v2-allow-all-mapper", - "consentRequired": false, - "config": {} - } - ], - "defaultClientScopes": [ - "web-origins", - "acr", - "roles", - "profile", - "basic", - "email" - ], - "optionalClientScopes": [ - "address", - "phone", - "offline_access", - "microprofile-jwt" - ] - } - ], - "clientScopes": [ - { - "id": "029c8cc4-a0b9-4c90-9f5f-63a408c7ee49", - "name": "offline_access", - "description": "OpenID Connect built-in scope: offline_access", - "protocol": "openid-connect", - "attributes": { - "consent.screen.text": "${offlineAccessScopeConsentText}", - "display.on.consent.screen": "true" + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : 0, + "protocolMappers" : [ { + "id" : "f2f24f2c-7f00-4c1b-862c-a1d821965330", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" } + }, { + "id" : "14db6a94-fd3f-40b2-95a2-139e329e51cf", + "name" : "docker-v2-allow-all-mapper", + "protocol" : "docker-v2", + "protocolMapper" : "docker-v2-allow-all-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + }, { + "id" : "d6877a14-f114-453c-a88c-dbe2472e4ed8", + "clientId" : "vegasco", + "name" : "Vegasco", + "description" : "", + "rootUrl" : "http://localhost/", + "adminUrl" : "", + "baseUrl" : "", + "surrogateAuthRequired" : false, + "enabled" : true, + "alwaysDisplayInConsole" : false, + "clientAuthenticatorType" : "client-secret", + "redirectUris" : [ "*" ], + "webOrigins" : [ ], + "notBefore" : 0, + "bearerOnly" : false, + "consentRequired" : false, + "standardFlowEnabled" : true, + "implicitFlowEnabled" : false, + "directAccessGrantsEnabled" : false, + "serviceAccountsEnabled" : false, + "publicClient" : true, + "frontchannelLogout" : true, + "protocol" : "openid-connect", + "attributes" : { + "oidc.ciba.grant.enabled" : "false", + "backchannel.logout.session.required" : "true", + "post.logout.redirect.uris" : "*", + "oauth2.device.authorization.grant.enabled" : "false", + "backchannel.logout.revoke.offline.tokens" : "false" }, - { - "id": "daf886da-43dc-424d-a089-bb564085b128", - "name": "roles", - "description": "OpenID Connect scope for add user roles to the access token", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "false", - "consent.screen.text": "${rolesScopeConsentText}", - "display.on.consent.screen": "true" - }, - "protocolMappers": [ - { - "id": "485ebff8-7f64-444a-aa59-446ab3e02c20", - "name": "audience resolve", - "protocol": "openid-connect", - "protocolMapper": "oidc-audience-resolve-mapper", - "consentRequired": false, - "config": { - "access.token.claim": "true", - "introspection.token.claim": "true" - } - }, - { - "id": "7e276de2-892a-457e-8437-0fa8d9029549", - "name": "client roles", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-client-role-mapper", - "consentRequired": false, - "config": { - "user.attribute": "foo", - "introspection.token.claim": "true", - "access.token.claim": "true", - "claim.name": "resource_access.${client_id}.roles", - "jsonType.label": "String", - "multivalued": "true" - } - }, - { - "id": "35b0b4fb-9f6d-4695-9cf6-c2fc99581d4c", - "name": "realm roles", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-realm-role-mapper", - "consentRequired": false, - "config": { - "user.attribute": "foo", - "introspection.token.claim": "true", - "access.token.claim": "true", - "claim.name": "realm_access.roles", - "jsonType.label": "String", - "multivalued": "true" - } - } - ] - }, - { - "id": "167dfb37-d340-4fe9-ae50-6e7cbaac4f31", - "name": "acr", - "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "false", - "display.on.consent.screen": "false" - }, - "protocolMappers": [ - { - "id": "09fc2f1e-8c67-44f8-b9fd-a837db569332", - "name": "acr loa level", - "protocol": "openid-connect", - "protocolMapper": "oidc-acr-mapper", - "consentRequired": false, - "config": { - "id.token.claim": "true", - "access.token.claim": "true", - "introspection.token.claim": "true" - } - } - ] - }, - { - "id": "c79fc776-2b2d-43d6-b12c-f57262311b6a", - "name": "profile", - "description": "OpenID Connect built-in scope: profile", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "consent.screen.text": "${profileScopeConsentText}", - "display.on.consent.screen": "true" - }, - "protocolMappers": [ - { - "id": "2cd6cd74-4a6a-4221-adc6-804b7227e1e5", - "name": "birthdate", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "birthdate", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "birthdate", - "jsonType.label": "String" - } - }, - { - "id": "0f7d36bb-7540-4063-8dcc-26c6408f6d94", - "name": "nickname", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "nickname", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "nickname", - "jsonType.label": "String" - } - }, - { - "id": "ce756133-7f49-43e9-8b32-ca358eddf877", - "name": "profile", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "profile", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "profile", - "jsonType.label": "String" - } - }, - { - "id": "1729b872-9632-4f52-8726-8d5a8e77d2ca", - "name": "middle name", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "middleName", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "middle_name", - "jsonType.label": "String" - } - }, - { - "id": "4db87e96-0bfd-4f05-91d1-ca675db6f74e", - "name": "website", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "website", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "website", - "jsonType.label": "String" - } - }, - { - "id": "0c589f3d-02ae-4584-b5d2-95c18c2c93be", - "name": "gender", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "gender", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "gender", - "jsonType.label": "String" - } - }, - { - "id": "985d5281-c952-47df-8ed4-6e34ba87e518", - "name": "picture", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "picture", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "picture", - "jsonType.label": "String" - } - }, - { - "id": "cd7d78c6-7eaa-4b92-8d70-d077d4bcee6c", - "name": "family name", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "lastName", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "family_name", - "jsonType.label": "String" - } - }, - { - "id": "5a0fd3a1-ab3a-4428-bb7c-de529a1021dc", - "name": "updated at", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "updatedAt", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "updated_at", - "jsonType.label": "long" - } - }, - { - "id": "7303fd26-77bc-45d6-8810-27e22706d3ff", - "name": "given name", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "firstName", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "given_name", - "jsonType.label": "String" - } - }, - { - "id": "1b9ef4aa-bd49-444c-9ba0-aa2178237beb", - "name": "full name", - "protocol": "openid-connect", - "protocolMapper": "oidc-full-name-mapper", - "consentRequired": false, - "config": { - "id.token.claim": "true", - "introspection.token.claim": "true", - "access.token.claim": "true", - "userinfo.token.claim": "true" - } - }, - { - "id": "aba2eec9-c04b-453a-95b7-001700e91eed", - "name": "username", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "username", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "preferred_username", - "jsonType.label": "String" - } - }, - { - "id": "cd17207d-9f14-4c36-991d-55eb4ef2825e", - "name": "zoneinfo", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "zoneinfo", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "zoneinfo", - "jsonType.label": "String" - } - }, - { - "id": "4aa38a97-aaaf-49bc-9f50-4b6c85bf8051", - "name": "locale", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "locale", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "locale", - "jsonType.label": "String" - } - } - ] - }, - { - "id": "90531319-c512-4266-9a2c-39ca2d6c4d19", - "name": "email", - "description": "OpenID Connect built-in scope: email", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "consent.screen.text": "${emailScopeConsentText}", - "display.on.consent.screen": "true" - }, - "protocolMappers": [ - { - "id": "3e138a1b-7dbf-4ac2-a8b4-300a0951ccb3", - "name": "email verified", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-property-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "emailVerified", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "email_verified", - "jsonType.label": "boolean" - } - }, - { - "id": "e50753c5-bfc5-400f-87b8-be7be8c23a87", - "name": "email", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "email", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "email", - "jsonType.label": "String" - } - } - ] - }, - { - "id": "26fc7ca8-3561-4b0f-a79e-e983ffac13c0", - "name": "phone", - "description": "OpenID Connect built-in scope: phone", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "consent.screen.text": "${phoneScopeConsentText}", - "display.on.consent.screen": "true" - }, - "protocolMappers": [ - { - "id": "5afe3ea6-523f-4619-bafa-02c8516af419", - "name": "phone number verified", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "phoneNumberVerified", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "phone_number_verified", - "jsonType.label": "boolean" - } - }, - { - "id": "7ce91fbe-4392-4126-a0df-18a6fb19c461", - "name": "phone number", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "phoneNumber", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "phone_number", - "jsonType.label": "String" - } - } - ] - }, - { - "id": "bfc650b4-2065-47df-807d-bd0efdb59a84", - "name": "web-origins", - "description": "OpenID Connect scope for add allowed web origins to the access token", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "false", - "consent.screen.text": "", - "display.on.consent.screen": "false" - }, - "protocolMappers": [ - { - "id": "ebafee7b-5762-45ff-8054-fad86428a70e", - "name": "allowed web origins", - "protocol": "openid-connect", - "protocolMapper": "oidc-allowed-origins-mapper", - "consentRequired": false, - "config": { - "access.token.claim": "true", - "introspection.token.claim": "true" - } - } - ] - }, - { - "id": "1ef07288-a201-466e-ad80-a1160ec4d84c", - "name": "basic", - "description": "OpenID Connect scope for add all basic claims to the token", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "false", - "display.on.consent.screen": "false" - }, - "protocolMappers": [ - { - "id": "264e35b8-e5c6-4607-a412-a4b021ade86d", - "name": "auth_time", - "protocol": "openid-connect", - "protocolMapper": "oidc-usersessionmodel-note-mapper", - "consentRequired": false, - "config": { - "user.session.note": "AUTH_TIME", - "id.token.claim": "true", - "introspection.token.claim": "true", - "access.token.claim": "true", - "claim.name": "auth_time", - "jsonType.label": "long" - } - }, - { - "id": "e2d85430-984e-4933-ab8a-095018599676", - "name": "sub", - "protocol": "openid-connect", - "protocolMapper": "oidc-sub-mapper", - "consentRequired": false, - "config": { - "access.token.claim": "true", - "introspection.token.claim": "true" - } - } - ] - }, - { - "id": "c2825d61-d98b-4200-9b7c-699635e4822e", - "name": "microprofile-jwt", - "description": "Microprofile - JWT built-in scope", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "display.on.consent.screen": "false" - }, - "protocolMappers": [ - { - "id": "02aef283-5cf6-4a5b-8879-0973efd8dd01", - "name": "groups", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-realm-role-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "multivalued": "true", - "user.attribute": "foo", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "groups", - "jsonType.label": "String" - } - }, - { - "id": "8938588f-e269-49c2-ad52-afe54224cdaf", - "name": "upn", - "protocol": "openid-connect", - "protocolMapper": "oidc-usermodel-attribute-mapper", - "consentRequired": false, - "config": { - "introspection.token.claim": "true", - "userinfo.token.claim": "true", - "user.attribute": "username", - "id.token.claim": "true", - "access.token.claim": "true", - "claim.name": "upn", - "jsonType.label": "String" - } - } - ] - }, - { - "id": "bfee5f0c-7b97-4569-bf06-942e6865e14c", - "name": "role_list", - "description": "SAML role list", - "protocol": "saml", - "attributes": { - "consent.screen.text": "${samlRoleListScopeConsentText}", - "display.on.consent.screen": "true" - }, - "protocolMappers": [ - { - "id": "09dddeb4-9b2f-45b4-8c60-3c1d62e64d75", - "name": "role list", - "protocol": "saml", - "protocolMapper": "saml-role-list-mapper", - "consentRequired": false, - "config": { - "single": "false", - "attribute.nameformat": "Basic", - "attribute.name": "Role" - } - } - ] - }, - { - "id": "773e90cb-bad8-438b-98c5-0174c799a37a", - "name": "address", - "description": "OpenID Connect built-in scope: address", - "protocol": "openid-connect", - "attributes": { - "include.in.token.scope": "true", - "consent.screen.text": "${addressScopeConsentText}", - "display.on.consent.screen": "true" - }, - "protocolMappers": [ - { - "id": "94842824-7c55-4576-ab27-e03079620f39", - "name": "address", - "protocol": "openid-connect", - "protocolMapper": "oidc-address-mapper", - "consentRequired": false, - "config": { - "user.attribute.formatted": "formatted", - "user.attribute.country": "country", - "introspection.token.claim": "true", - "user.attribute.postal_code": "postal_code", - "userinfo.token.claim": "true", - "user.attribute.street": "street", - "id.token.claim": "true", - "user.attribute.region": "region", - "access.token.claim": "true", - "user.attribute.locality": "locality" - } - } - ] + "authenticationFlowBindingOverrides" : { }, + "fullScopeAllowed" : false, + "nodeReRegistrationTimeout" : -1, + "protocolMappers" : [ { + "id" : "514219d4-0807-44c2-90e8-310634357c0e", + "name" : "Vegasco_Audience_Mapper", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-mapper", + "consentRequired" : false, + "config" : { + "included.client.audience" : "vegasco", + "id.token.claim" : "false", + "lightweight.claim" : "false", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "false" + } + }, { + "id" : "de5204df-ee72-4105-9640-cc01ddf08b18", + "name" : "docker-v2-allow-all-mapper", + "protocol" : "docker-v2", + "protocolMapper" : "docker-v2-allow-all-mapper", + "consentRequired" : false, + "config" : { } + } ], + "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], + "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] + } ], + "clientScopes" : [ { + "id" : "029c8cc4-a0b9-4c90-9f5f-63a408c7ee49", + "name" : "offline_access", + "description" : "OpenID Connect built-in scope: offline_access", + "protocol" : "openid-connect", + "attributes" : { + "consent.screen.text" : "${offlineAccessScopeConsentText}", + "display.on.consent.screen" : "true" } - ], - "defaultDefaultClientScopes": [ - "role_list", - "profile", - "email", - "roles", - "web-origins", - "acr", - "basic" - ], - "defaultOptionalClientScopes": [ - "offline_access", - "address", - "phone", - "microprofile-jwt" - ], - "browserSecurityHeaders": { - "contentSecurityPolicyReportOnly": "", - "xContentTypeOptions": "nosniff", - "referrerPolicy": "no-referrer", - "xRobotsTag": "none", - "xFrameOptions": "SAMEORIGIN", - "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", - "xXSSProtection": "1; mode=block", - "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, { + "id" : "daf886da-43dc-424d-a089-bb564085b128", + "name" : "roles", + "description" : "OpenID Connect scope for add user roles to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "consent.screen.text" : "${rolesScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "485ebff8-7f64-444a-aa59-446ab3e02c20", + "name" : "audience resolve", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-audience-resolve-mapper", + "consentRequired" : false, + "config" : { + "access.token.claim" : "true", + "introspection.token.claim" : "true" + } + }, { + "id" : "7e276de2-892a-457e-8437-0fa8d9029549", + "name" : "client roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-client-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "resource_access.${client_id}.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + }, { + "id" : "35b0b4fb-9f6d-4695-9cf6-c2fc99581d4c", + "name" : "realm roles", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "user.attribute" : "foo", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "realm_access.roles", + "jsonType.label" : "String", + "multivalued" : "true" + } + } ] + }, { + "id" : "167dfb37-d340-4fe9-ae50-6e7cbaac4f31", + "name" : "acr", + "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "09fc2f1e-8c67-44f8-b9fd-a837db569332", + "name" : "acr loa level", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-acr-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "access.token.claim" : "true", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true" + } + } ] + }, { + "id" : "c79fc776-2b2d-43d6-b12c-f57262311b6a", + "name" : "profile", + "description" : "OpenID Connect built-in scope: profile", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${profileScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "2cd6cd74-4a6a-4221-adc6-804b7227e1e5", + "name" : "birthdate", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "birthdate", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "birthdate", + "jsonType.label" : "String" + } + }, { + "id" : "0f7d36bb-7540-4063-8dcc-26c6408f6d94", + "name" : "nickname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "nickname", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "nickname", + "jsonType.label" : "String" + } + }, { + "id" : "ce756133-7f49-43e9-8b32-ca358eddf877", + "name" : "profile", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "profile", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "profile", + "jsonType.label" : "String" + } + }, { + "id" : "1729b872-9632-4f52-8726-8d5a8e77d2ca", + "name" : "middle name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "middleName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "middle_name", + "jsonType.label" : "String" + } + }, { + "id" : "4db87e96-0bfd-4f05-91d1-ca675db6f74e", + "name" : "website", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "website", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "website", + "jsonType.label" : "String" + } + }, { + "id" : "0c589f3d-02ae-4584-b5d2-95c18c2c93be", + "name" : "gender", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "gender", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "gender", + "jsonType.label" : "String" + } + }, { + "id" : "985d5281-c952-47df-8ed4-6e34ba87e518", + "name" : "picture", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "picture", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "picture", + "jsonType.label" : "String" + } + }, { + "id" : "cd7d78c6-7eaa-4b92-8d70-d077d4bcee6c", + "name" : "family name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "lastName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "family_name", + "jsonType.label" : "String" + } + }, { + "id" : "5a0fd3a1-ab3a-4428-bb7c-de529a1021dc", + "name" : "updated at", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "updatedAt", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "updated_at", + "jsonType.label" : "long" + } + }, { + "id" : "7303fd26-77bc-45d6-8810-27e22706d3ff", + "name" : "given name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "firstName", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "given_name", + "jsonType.label" : "String" + } + }, { + "id" : "1b9ef4aa-bd49-444c-9ba0-aa2178237beb", + "name" : "full name", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-full-name-mapper", + "consentRequired" : false, + "config" : { + "id.token.claim" : "true", + "introspection.token.claim" : "true", + "access.token.claim" : "true", + "userinfo.token.claim" : "true" + } + }, { + "id" : "aba2eec9-c04b-453a-95b7-001700e91eed", + "name" : "username", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "preferred_username", + "jsonType.label" : "String" + } + }, { + "id" : "cd17207d-9f14-4c36-991d-55eb4ef2825e", + "name" : "zoneinfo", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "zoneinfo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "zoneinfo", + "jsonType.label" : "String" + } + }, { + "id" : "4aa38a97-aaaf-49bc-9f50-4b6c85bf8051", + "name" : "locale", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "locale", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "locale", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "90531319-c512-4266-9a2c-39ca2d6c4d19", + "name" : "email", + "description" : "OpenID Connect built-in scope: email", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${emailScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "3e138a1b-7dbf-4ac2-a8b4-300a0951ccb3", + "name" : "email verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-property-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "emailVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "e50753c5-bfc5-400f-87b8-be7be8c23a87", + "name" : "email", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "email", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "email", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "26fc7ca8-3561-4b0f-a79e-e983ffac13c0", + "name" : "phone", + "description" : "OpenID Connect built-in scope: phone", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${phoneScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "5afe3ea6-523f-4619-bafa-02c8516af419", + "name" : "phone number verified", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumberVerified", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number_verified", + "jsonType.label" : "boolean" + } + }, { + "id" : "7ce91fbe-4392-4126-a0df-18a6fb19c461", + "name" : "phone number", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "phoneNumber", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "phone_number", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "bfc650b4-2065-47df-807d-bd0efdb59a84", + "name" : "web-origins", + "description" : "OpenID Connect scope for add allowed web origins to the access token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "consent.screen.text" : "", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "ebafee7b-5762-45ff-8054-fad86428a70e", + "name" : "allowed web origins", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-allowed-origins-mapper", + "consentRequired" : false, + "config" : { + "access.token.claim" : "true", + "introspection.token.claim" : "true" + } + } ] + }, { + "id" : "1ef07288-a201-466e-ad80-a1160ec4d84c", + "name" : "basic", + "description" : "OpenID Connect scope for add all basic claims to the token", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "false", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "264e35b8-e5c6-4607-a412-a4b021ade86d", + "name" : "auth_time", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usersessionmodel-note-mapper", + "consentRequired" : false, + "config" : { + "user.session.note" : "AUTH_TIME", + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "auth_time", + "jsonType.label" : "long" + } + }, { + "id" : "e2d85430-984e-4933-ab8a-095018599676", + "name" : "sub", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-sub-mapper", + "consentRequired" : false, + "config" : { + "access.token.claim" : "true", + "introspection.token.claim" : "true" + } + } ] + }, { + "id" : "c2825d61-d98b-4200-9b7c-699635e4822e", + "name" : "microprofile-jwt", + "description" : "Microprofile - JWT built-in scope", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "display.on.consent.screen" : "false" + }, + "protocolMappers" : [ { + "id" : "02aef283-5cf6-4a5b-8879-0973efd8dd01", + "name" : "groups", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-realm-role-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "multivalued" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "foo", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "groups", + "jsonType.label" : "String" + } + }, { + "id" : "8938588f-e269-49c2-ad52-afe54224cdaf", + "name" : "upn", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "introspection.token.claim" : "true", + "userinfo.token.claim" : "true", + "user.attribute" : "username", + "id.token.claim" : "true", + "access.token.claim" : "true", + "claim.name" : "upn", + "jsonType.label" : "String" + } + } ] + }, { + "id" : "bfee5f0c-7b97-4569-bf06-942e6865e14c", + "name" : "role_list", + "description" : "SAML role list", + "protocol" : "saml", + "attributes" : { + "consent.screen.text" : "${samlRoleListScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "09dddeb4-9b2f-45b4-8c60-3c1d62e64d75", + "name" : "role list", + "protocol" : "saml", + "protocolMapper" : "saml-role-list-mapper", + "consentRequired" : false, + "config" : { + "single" : "false", + "attribute.nameformat" : "Basic", + "attribute.name" : "Role" + } + } ] + }, { + "id" : "773e90cb-bad8-438b-98c5-0174c799a37a", + "name" : "address", + "description" : "OpenID Connect built-in scope: address", + "protocol" : "openid-connect", + "attributes" : { + "include.in.token.scope" : "true", + "consent.screen.text" : "${addressScopeConsentText}", + "display.on.consent.screen" : "true" + }, + "protocolMappers" : [ { + "id" : "94842824-7c55-4576-ab27-e03079620f39", + "name" : "address", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-address-mapper", + "consentRequired" : false, + "config" : { + "user.attribute.formatted" : "formatted", + "user.attribute.country" : "country", + "introspection.token.claim" : "true", + "user.attribute.postal_code" : "postal_code", + "userinfo.token.claim" : "true", + "user.attribute.street" : "street", + "id.token.claim" : "true", + "user.attribute.region" : "region", + "access.token.claim" : "true", + "user.attribute.locality" : "locality" + } + } ] + } ], + "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr", "basic" ], + "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], + "browserSecurityHeaders" : { + "contentSecurityPolicyReportOnly" : "", + "xContentTypeOptions" : "nosniff", + "referrerPolicy" : "no-referrer", + "xRobotsTag" : "none", + "xFrameOptions" : "SAMEORIGIN", + "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection" : "1; mode=block", + "strictTransportSecurity" : "max-age=31536000; includeSubDomains" }, - "smtpServer": {}, - "eventsEnabled": false, - "eventsListeners": [ - "jboss-logging" - ], - "enabledEventTypes": [], - "adminEventsEnabled": false, - "adminEventsDetailsEnabled": false, - "identityProviders": [], - "identityProviderMappers": [], - "components": { - "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ - { - "id": "87c77663-33a5-41fa-805e-ad6e16ac693a", - "name": "Allowed Client Scopes", - "providerId": "allowed-client-templates", - "subType": "anonymous", - "subComponents": {}, - "config": { - "allow-default-scopes": [ - "true" - ] - } - }, - { - "id": "93b727d6-92be-4ae7-9436-cd8a67e13576", - "name": "Allowed Protocol Mapper Types", - "providerId": "allowed-protocol-mappers", - "subType": "authenticated", - "subComponents": {}, - "config": { - "allowed-protocol-mapper-types": [ - "saml-user-property-mapper", - "oidc-address-mapper", - "saml-role-list-mapper", - "oidc-usermodel-property-mapper", - "oidc-usermodel-attribute-mapper", - "oidc-sha256-pairwise-sub-mapper", - "oidc-full-name-mapper", - "saml-user-attribute-mapper" - ] - } - }, - { - "id": "b099d087-5954-460d-902f-def7799cb005", - "name": "Allowed Client Scopes", - "providerId": "allowed-client-templates", - "subType": "authenticated", - "subComponents": {}, - "config": { - "allow-default-scopes": [ - "true" - ] - } - }, - { - "id": "16f67ef8-1431-4241-a7a3-0849513dd422", - "name": "Allowed Protocol Mapper Types", - "providerId": "allowed-protocol-mappers", - "subType": "anonymous", - "subComponents": {}, - "config": { - "allowed-protocol-mapper-types": [ - "oidc-full-name-mapper", - "saml-user-attribute-mapper", - "oidc-address-mapper", - "oidc-usermodel-attribute-mapper", - "oidc-usermodel-property-mapper", - "saml-role-list-mapper", - "saml-user-property-mapper", - "oidc-sha256-pairwise-sub-mapper" - ] - } - }, - { - "id": "e86226d6-0944-4c08-b809-d72e6c7991c4", - "name": "Max Clients Limit", - "providerId": "max-clients", - "subType": "anonymous", - "subComponents": {}, - "config": { - "max-clients": [ - "200" - ] - } - }, - { - "id": "c796a096-9c20-4983-b7bc-cb282936040f", - "name": "Consent Required", - "providerId": "consent-required", - "subType": "anonymous", - "subComponents": {}, - "config": {} - }, - { - "id": "76051f4c-b020-449d-a40f-664f918d0082", - "name": "Trusted Hosts", - "providerId": "trusted-hosts", - "subType": "anonymous", - "subComponents": {}, - "config": { - "host-sending-registration-request-must-match": [ - "true" - ], - "client-uris-must-match": [ - "true" - ] - } - }, - { - "id": "055d0fd2-9546-466d-b945-914f5ce84272", - "name": "Full Scope Disabled", - "providerId": "scope", - "subType": "anonymous", - "subComponents": {}, - "config": {} + "smtpServer" : { }, + "eventsEnabled" : false, + "eventsListeners" : [ "jboss-logging" ], + "enabledEventTypes" : [ ], + "adminEventsEnabled" : false, + "adminEventsDetailsEnabled" : false, + "identityProviders" : [ ], + "identityProviderMappers" : [ ], + "components" : { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { + "id" : "87c77663-33a5-41fa-805e-ad6e16ac693a", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] } - ], - "org.keycloak.keys.KeyProvider": [ - { - "id": "bb2e382d-8ed7-41ab-b545-defd7cf035dd", - "name": "rsa-enc-generated", - "providerId": "rsa-enc-generated", - "subComponents": {}, - "config": { - "priority": [ - "100" - ], - "algorithm": [ - "RSA-OAEP" - ] - } - }, - { - "id": "ca7e3c60-4007-4fbd-bb58-80989d4ef95f", - "name": "aes-generated", - "providerId": "aes-generated", - "subComponents": {}, - "config": { - "priority": [ - "100" - ] - } - }, - { - "id": "5ddf1462-4890-492a-919d-5a4cf34d8e74", - "name": "rsa-generated", - "providerId": "rsa-generated", - "subComponents": {}, - "config": { - "priority": [ - "100" - ] - } - }, - { - "id": "64782b75-6f52-4f1d-af06-09ad6bfcdfb9", - "name": "hmac-generated-hs512", - "providerId": "hmac-generated", - "subComponents": {}, - "config": { - "priority": [ - "100" - ], - "algorithm": [ - "HS512" - ] - } + }, { + "id" : "93b727d6-92be-4ae7-9436-cd8a67e13576", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "saml-user-property-mapper", "oidc-address-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper" ] } - ] + }, { + "id" : "b099d087-5954-460d-902f-def7799cb005", + "name" : "Allowed Client Scopes", + "providerId" : "allowed-client-templates", + "subType" : "authenticated", + "subComponents" : { }, + "config" : { + "allow-default-scopes" : [ "true" ] + } + }, { + "id" : "16f67ef8-1431-4241-a7a3-0849513dd422", + "name" : "Allowed Protocol Mapper Types", + "providerId" : "allowed-protocol-mappers", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "saml-role-list-mapper", "saml-user-property-mapper" ] + } + }, { + "id" : "e86226d6-0944-4c08-b809-d72e6c7991c4", + "name" : "Max Clients Limit", + "providerId" : "max-clients", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "max-clients" : [ "200" ] + } + }, { + "id" : "c796a096-9c20-4983-b7bc-cb282936040f", + "name" : "Consent Required", + "providerId" : "consent-required", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + }, { + "id" : "76051f4c-b020-449d-a40f-664f918d0082", + "name" : "Trusted Hosts", + "providerId" : "trusted-hosts", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { + "host-sending-registration-request-must-match" : [ "true" ], + "client-uris-must-match" : [ "true" ] + } + }, { + "id" : "055d0fd2-9546-466d-b945-914f5ce84272", + "name" : "Full Scope Disabled", + "providerId" : "scope", + "subType" : "anonymous", + "subComponents" : { }, + "config" : { } + } ], + "org.keycloak.keys.KeyProvider" : [ { + "id" : "bb2e382d-8ed7-41ab-b545-defd7cf035dd", + "name" : "rsa-enc-generated", + "providerId" : "rsa-enc-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEAp+k3VByvA2qSCFXXxzJpeEUlzZiqHhWbNYy2dbd/VIBNOsaFr97fSpL1bOdEqJ20Q2xfcrI/u+Ep3CbZoHswGpfJfNU4d+yvbIFDFFavBNmtAeTxw6NiIIIvJGwnxfzed6AD7VRLDDUTLseawSesnwVWtJcF/NNiKED+CZzaXhmQBqwOrwVDlQMRUwmOhoOQWoKxnqWcvO0Gg/DFZCMT9GMwfnMRKRJE2aABix6b90aDK9AoIGtAILALOiprd2qcm5NMh9ztdSP3RpTWpvO1WqkDuxkTAL7lsc2/CymH3IQtCrQkI6f+aLZ7Xk4U9EOeq4V5HLDbHB/0fwAfyytoxwIDAQABAoIBAACllioTr0AHVsSiXzi467uH0IA+8867lLHcscPNZ3UlLNKBrjDCBzZgHEVmtNQ5Di43HEdq7mgKkzqGqKq3+aVS0cO99kpD+eMFniH0Q8VVgyv2b7dDwcyiD0VZybWD9f8wp269HWinsJDjRFEMNRkWNDK4+X4R+iZxWNnRTxYed0pdOTfWFX+iyQRefhOzReZjyxoM8xUmehiMpl4X/wYo6QxOW28ZKAtgZKDfiBymXmG1hMbk75Ktm5z5iy1vceZn19CfK56kTA4Y3OtOFwHaBu5Oq+4fgByS+EVv4dVAcR5mtKJV3z34L8xjg/J4dnosn7ZXMmCC5UWxb944Zu0CgYEA2q0zGuBaAwjjwE7AUtd+lpKJrEtm/3FzUXCSxpcgr6yGMcOaZlQKPNp+QnFOHo59O0CQqsDU7t/TRyNDKhtc4r1KYR+n8KHnW/l/bA3GIZtydFW+OcUL4lQGm1ZyVo3UsFaTCRa59TeFpDIX5gWS+/CeqgGuUdBNLlbg5D4LItsCgYEAxJHg4yhEDNpHf8oH7IlUB1NYwMX1pv0xgK9RYksCB2Z+Dec8JI9bheeb6S0pb0rkbLrwjVyNbV4/dZn6oYfLzI5gdu5/qKDxy5g+tg3C2VkLyT3ONM4IPKoCt86ZokFdzqItPPVWYZ0DUr78yzpJDjgnB84Y6HbOGx95IneT94UCgYAkIucFE/oL8lYgm2Lwzaefnkud6z/0Cn1yAdZfdu3x2eK7KoXDTzP55mli9XJhXk6Xkg3WCdOmPdqeMNeSh78LwRgfgKmx/C9NZaeG5afOOe/qBZlP1p4mIpiM5vYyE3IISeY2ZEkKmsg84AJPArDNbW/qzChQYMnAVJ8JWK9ibQKBgHBqnRpMJN7U1p8Wg4Ga6BtoZxGYJOzjUDQwD3MPQpHI474/x/2Anu7tjhTEZzXmtswX/QpbK+aoR9KRxOwsJTlPE4vwycE+ignNf8/N/ukeK8djOVKpobxP3k4QMXzBtUw/I3ABPu2ERipEX346Tx16r5efHk+T4jtQvI4hpNWhAoGBAK493H9KZQdjlMcoMvHS2wH26vLRIOA1/urC5KKMSHqcTdFneXFdz55u78uuCCRC9Qmv89RvWyfp7asadymtaFYKQGZnGUvNpNOm+c/nPy2bywrCHcUWU8OYUyrJM3m2AQuSwQVyKjWCJrAtclomd2fLvpJ1KF26l0fq1BNF23DS" ], + "certificate" : [ "MIICpTCCAY0CBgGRI/HPRTANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtkZXZlbG9wbWVudDAeFw0yNDA4MDUxOTA3MTVaFw0zNDA4MDUxOTA4NTVaMBYxFDASBgNVBAMMC2RldmVsb3BtZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp+k3VByvA2qSCFXXxzJpeEUlzZiqHhWbNYy2dbd/VIBNOsaFr97fSpL1bOdEqJ20Q2xfcrI/u+Ep3CbZoHswGpfJfNU4d+yvbIFDFFavBNmtAeTxw6NiIIIvJGwnxfzed6AD7VRLDDUTLseawSesnwVWtJcF/NNiKED+CZzaXhmQBqwOrwVDlQMRUwmOhoOQWoKxnqWcvO0Gg/DFZCMT9GMwfnMRKRJE2aABix6b90aDK9AoIGtAILALOiprd2qcm5NMh9ztdSP3RpTWpvO1WqkDuxkTAL7lsc2/CymH3IQtCrQkI6f+aLZ7Xk4U9EOeq4V5HLDbHB/0fwAfyytoxwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCUtjMYZWBiildDYkd2iI8b8G1n6qvZ1q+x/JIYUNVbFWUfC7lfiCIua6YwgJluI4RcZ5hFpHWWxzW/Cz8RTymsM7oCwGa3Nv5cUZLGOB4B6FC3nDNdJm1WP2ICqsu9HpS6BReS8WCTQ+r+zgE/hO9SCtffuEAmib+ICKOE7KhYkqveN835la9rlfVbgbKl7aHdtJz+JqFEdgAM+kdpuT+csINuoXQpn5MWBs1wvdQNu7tA7m01vCaJg00a43FxSXJjq/lxUGXLdRoAqe0SFqkMnpdqfV8XZClA9Kgs6FBFG6xKikJLeD5M61UA/P8WXHEgPbWcy1chFYQyeimmxEzl" ], + "priority" : [ "100" ], + "algorithm" : [ "RSA-OAEP" ] + } + }, { + "id" : "ca7e3c60-4007-4fbd-bb58-80989d4ef95f", + "name" : "aes-generated", + "providerId" : "aes-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "40b90151-b1ef-4e82-a179-7dbc45ed2db3" ], + "secret" : [ "o1R5W3Ez-r_9PK6xtUJfFg" ], + "priority" : [ "100" ] + } + }, { + "id" : "5ddf1462-4890-492a-919d-5a4cf34d8e74", + "name" : "rsa-generated", + "providerId" : "rsa-generated", + "subComponents" : { }, + "config" : { + "privateKey" : [ "MIIEowIBAAKCAQEAuPtq+FpY5j7YFdxxNRcpNooCMqYcU7h2s2DZs3fpBURAR3WTQ/kq9KuNnlub8GSGuoKKZGL6dE6cQmfGyB+7OAHuwc19R/mVfTFC8SFLQHbdPfPtZJai2PR0OuW7zM6XY/lItaUN0+b8qSzQ1ymSakC6vY8//A+ZGpta55vveJCdx3gTI7BowvOE1HBRM/2cl1I6MwQ9Wb7AJHIcoaDUcv5PX7iW3gP7CcMbyLR6fkIvPZ85IldSjJu/k4uSKH5Xpv0sDen9YJjvWubjnisAoLVjNmky78KWcZ7xxPXIHRuZv6OsHl2KrkQEOJHL2EU2S7GlgNFCGMfo9wQrvPwyiQIDAQABAoIBAFqdjsEqQPJJRsEuw6YmOmIlKP52sNmyOzOT9hEcZGHYFUTsK7/Yh1pjz3QLnFAeboFfATw74ESpXZKKE65lHOlKyRLW/tALS1eQkiJdFOf3UlnO7DOiNxPgbC+N6nlhmne23g9dU3DCbnLRKVy9WKQEIXZKfiWT3oRSJi3fDWgZWc3Ds72D/T4Oy8go1zMbHIe5kpzhScoL72YwaefsqwNFtY0sqts0uQF2mVXXeFfq2lvgcrob9sfiDx+NW5m3In8Qa0+VL/oeYvgbUPi3oi2LxH6xDHbQUKq4DUKBlPMuaSW18E1xXV6qUu4DxMA8+eqo4KLAVDyudSmPQj3gsCECgYEA68Seb4NUvfGRfSMGouEfAJfYf2MoEW5VdIbgVoVYlJ9mI3zjdLsHsfmXmrMnvQWBx9YGL+XRjcpTsPzH0DAD2+JjFzIukv7l6osG67JA3i5GHYXuFL3Ml0MHCWvbSgrHdDAm2/HroifZEbPBjFsq8Kb1wFQ0AR7ClMdgxrDMJOcCgYEAyNsgkIrtB11gKlc8pf/LByWjfsueXfhhHk5G0RSCyZ6DXlK1C55te9tJ+Y2Z5VjUaqsRbIqNKVRL1CRZQzjSzYAxYPaFYvy6gXmTukQB5/i8D/TSjXNfxOpEBTnKX4psas51TS4ncJg35oXoh+zahqawczYs7Ein254n3J4vjw8CgYBn1Jlpxs7FL+PA1nIPvVDn8/d2cnas5ohf3x5hPCx8l45lxpRtTgjfimoHySqRBiHXnhvvcCjPZvFgmpJszxiD97ln98OnHPaoSj3sAv6qWnqqedcV71hwrSYmMgfLHeAk/Se/6VS6fw4Ly7xLUcMhZIYKA4s7iw5qczvdhPMCtQKBgQCbLCc5ZAsA4JO1wlW5jSeGKv7nq1l7EbO+HQ065BSyvQKSsWTrSAcfY+f/ovTdKcZZbjX03Al4f4Zhq39GnrTFTJ9ZYLrmIYfZFVsa0QWD+DcaQLMV0qePUskgHGiLbT6bOUuIR/ryUrcFIjjmIgcldcvmBlmoZe1AjywOlir54wKBgAuPjALJsstfSbI7ER+acyTNTTlVxVn9CqKWv5UTeLNpIBBjSi36jYVAGmpCY2rgHVZE5EZCejiCn18mdMVQS3+G5Dmb9vAldoxaRim5oMSbnyJt3btszf29iM2nFzw/aGKzHVKDWI473SA8krDtvvHsBEDEEKHNPlF4TE+aowUV" ], + "certificate" : [ "MIICpTCCAY0CBgGRI/HPwjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtkZXZlbG9wbWVudDAeFw0yNDA4MDUxOTA3MTVaFw0zNDA4MDUxOTA4NTVaMBYxFDASBgNVBAMMC2RldmVsb3BtZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuPtq+FpY5j7YFdxxNRcpNooCMqYcU7h2s2DZs3fpBURAR3WTQ/kq9KuNnlub8GSGuoKKZGL6dE6cQmfGyB+7OAHuwc19R/mVfTFC8SFLQHbdPfPtZJai2PR0OuW7zM6XY/lItaUN0+b8qSzQ1ymSakC6vY8//A+ZGpta55vveJCdx3gTI7BowvOE1HBRM/2cl1I6MwQ9Wb7AJHIcoaDUcv5PX7iW3gP7CcMbyLR6fkIvPZ85IldSjJu/k4uSKH5Xpv0sDen9YJjvWubjnisAoLVjNmky78KWcZ7xxPXIHRuZv6OsHl2KrkQEOJHL2EU2S7GlgNFCGMfo9wQrvPwyiQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBqMz0QkjIrMnmF3VrYqdfHsgZptyI6jj1GQ0MR4qJu4Dl5ojhGXFukbAu8xBjnVhv1uc6IWjfWT7YJbUc6n752uQH3//bK46K/vXhbGMP+lCpyc5CT69FOJtutT+/A7R6/8WS6M91BwYF9pX3XHQ1109ylAzhW+qV1LqdJMipy4FB7yYDImLsf6Z8JtmSveopwMQ8A/GJTG5LEPnoYqJ8BY85yG/GszgXcn63f6iTT/7BceKOu7kp0l+Np6qooMddniV4917P+aCU/T7dkz+afj0JURf1fnsITXu02iyi+aHmHihOuYzqobeXDJU0FqGg0qQGJU9QLye5jbZqFM2yf" ], + "priority" : [ "100" ] + } + }, { + "id" : "64782b75-6f52-4f1d-af06-09ad6bfcdfb9", + "name" : "hmac-generated-hs512", + "providerId" : "hmac-generated", + "subComponents" : { }, + "config" : { + "kid" : [ "1c986f4a-6e05-43e1-97a8-d756aed963ff" ], + "secret" : [ "EWGJDe4oUzHqg-arMOUMeEIqb1k0l8O_RpYsmhHl0oiTDqVFnlHB3cBAyhPQYdEkkeFoVnjBA5t46zxj4sOLAMZ9McOgifAe_WBe4_RrxQDhMFMLMNyWnCnFP_jDAFj98P0xyv8atwZCG5xPRFDrO9pJKxO472kG6ws0CZ-CpdY" ], + "priority" : [ "100" ], + "algorithm" : [ "HS512" ] + } + } ] }, - "internationalizationEnabled": false, - "supportedLocales": [], - "authenticationFlows": [ - { - "id": "17180a8f-635e-42e3-9870-88b7b61fb915", - "alias": "Account verification options", - "description": "Method with which to verity the existing account", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "idp-email-verification", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "ALTERNATIVE", - "priority": 20, - "autheticatorFlow": true, - "flowAlias": "Verify Existing Account by Re-authentication", - "userSetupAllowed": false - } - ] - }, - { - "id": "c416b7a7-45c8-4f68-99b9-0fd7d817fe18", - "alias": "Browser - Conditional OTP", - "description": "Flow to determine if the OTP is required for the authentication", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "conditional-user-configured", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "auth-otp-form", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "7d9a6e6e-6a2e-4453-aac4-fd2e0f158f4b", - "alias": "Direct Grant - Conditional OTP", - "description": "Flow to determine if the OTP is required for the authentication", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "conditional-user-configured", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "direct-grant-validate-otp", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "5a9053a6-432c-4380-b267-96f86cfac2a7", - "alias": "First broker login - Conditional OTP", - "description": "Flow to determine if the OTP is required for the authentication", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "conditional-user-configured", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "auth-otp-form", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "f12fa4ea-704b-4f25-8a50-2b6c011d0249", - "alias": "Handle Existing Account", - "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "idp-confirm-link", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": true, - "flowAlias": "Account verification options", - "userSetupAllowed": false - } - ] - }, - { - "id": "1fe6bf4e-f2f8-4631-b024-6e8a5bd8c42e", - "alias": "Reset - Conditional OTP", - "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "conditional-user-configured", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "reset-otp", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "6246dbfb-a619-4635-ab3e-b394f0ed4af3", - "alias": "User creation or linking", - "description": "Flow for the existing/non-existing user alternatives", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticatorConfig": "create unique user config", - "authenticator": "idp-create-user-if-unique", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "ALTERNATIVE", - "priority": 20, - "autheticatorFlow": true, - "flowAlias": "Handle Existing Account", - "userSetupAllowed": false - } - ] - }, - { - "id": "efa63a87-ea5c-4519-a323-11099438f81e", - "alias": "Verify Existing Account by Re-authentication", - "description": "Reauthentication of existing account", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "idp-username-password-form", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "CONDITIONAL", - "priority": 20, - "autheticatorFlow": true, - "flowAlias": "First broker login - Conditional OTP", - "userSetupAllowed": false - } - ] - }, - { - "id": "2cd74a88-7aff-42ad-a94e-776089c0aaca", - "alias": "browser", - "description": "browser based authentication", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "auth-cookie", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "auth-spnego", - "authenticatorFlow": false, - "requirement": "DISABLED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "identity-provider-redirector", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 25, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "ALTERNATIVE", - "priority": 30, - "autheticatorFlow": true, - "flowAlias": "forms", - "userSetupAllowed": false - } - ] - }, - { - "id": "f14b6e2e-bf4f-4dc3-906e-8e417cb8a96b", - "alias": "clients", - "description": "Base authentication for clients", - "providerId": "client-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "client-secret", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "client-jwt", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "client-secret-jwt", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 30, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "client-x509", - "authenticatorFlow": false, - "requirement": "ALTERNATIVE", - "priority": 40, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "704eac4a-0f3f-42b1-8326-ed8193f7c5e0", - "alias": "direct grant", - "description": "OpenID Connect Resource Owner Grant", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "direct-grant-validate-username", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "direct-grant-validate-password", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "CONDITIONAL", - "priority": 30, - "autheticatorFlow": true, - "flowAlias": "Direct Grant - Conditional OTP", - "userSetupAllowed": false - } - ] - }, - { - "id": "ea358460-4bdb-4926-8bdb-9ae28b76a73a", - "alias": "docker auth", - "description": "Used by Docker clients to authenticate against the IDP", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "docker-http-basic-authenticator", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "064b83a9-eff4-47c0-a885-5a2552a390b7", - "alias": "first broker login", - "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticatorConfig": "review profile config", - "authenticator": "idp-review-profile", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": true, - "flowAlias": "User creation or linking", - "userSetupAllowed": false - } - ] - }, - { - "id": "224e3374-9c81-437a-9abb-557eba90dcea", - "alias": "forms", - "description": "Username, password, otp and other auth forms.", - "providerId": "basic-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "auth-username-password-form", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "CONDITIONAL", - "priority": 20, - "autheticatorFlow": true, - "flowAlias": "Browser - Conditional OTP", - "userSetupAllowed": false - } - ] - }, - { - "id": "ccfe54ac-0522-45a5-8cf5-a3bd89396ea8", - "alias": "registration", - "description": "registration flow", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "registration-page-form", - "authenticatorFlow": true, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": true, - "flowAlias": "registration form", - "userSetupAllowed": false - } - ] - }, - { - "id": "09319bf1-dc33-4d5a-9866-20ef01e3364e", - "alias": "registration form", - "description": "registration form", - "providerId": "form-flow", - "topLevel": false, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "registration-user-creation", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "registration-password-action", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 50, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "registration-recaptcha-action", - "authenticatorFlow": false, - "requirement": "DISABLED", - "priority": 60, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "registration-terms-and-conditions", - "authenticatorFlow": false, - "requirement": "DISABLED", - "priority": 70, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] - }, - { - "id": "e55eed94-e227-4edc-af34-5d8595fb2379", - "alias": "reset credentials", - "description": "Reset credentials for a user if they forgot their password or something", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "reset-credentials-choose-user", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "reset-credential-email", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 20, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticator": "reset-password", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 30, - "autheticatorFlow": false, - "userSetupAllowed": false - }, - { - "authenticatorFlow": true, - "requirement": "CONDITIONAL", - "priority": 40, - "autheticatorFlow": true, - "flowAlias": "Reset - Conditional OTP", - "userSetupAllowed": false - } - ] - }, - { - "id": "4ce089cb-31f2-4c4c-99a8-fd7f2c6b458e", - "alias": "saml ecp", - "description": "SAML ECP Profile Authentication Flow", - "providerId": "basic-flow", - "topLevel": true, - "builtIn": true, - "authenticationExecutions": [ - { - "authenticator": "http-basic-authenticator", - "authenticatorFlow": false, - "requirement": "REQUIRED", - "priority": 10, - "autheticatorFlow": false, - "userSetupAllowed": false - } - ] + "internationalizationEnabled" : false, + "supportedLocales" : [ ], + "authenticationFlows" : [ { + "id" : "17180a8f-635e-42e3-9870-88b7b61fb915", + "alias" : "Account verification options", + "description" : "Method with which to verity the existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-email-verification", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Verify Existing Account by Re-authentication", + "userSetupAllowed" : false + } ] + }, { + "id" : "c416b7a7-45c8-4f68-99b9-0fd7d817fe18", + "alias" : "Browser - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "7d9a6e6e-6a2e-4453-aac4-fd2e0f158f4b", + "alias" : "Direct Grant - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "5a9053a6-432c-4380-b267-96f86cfac2a7", + "alias" : "First broker login - Conditional OTP", + "description" : "Flow to determine if the OTP is required for the authentication", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-otp-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "f12fa4ea-704b-4f25-8a50-2b6c011d0249", + "alias" : "Handle Existing Account", + "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-confirm-link", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Account verification options", + "userSetupAllowed" : false + } ] + }, { + "id" : "1fe6bf4e-f2f8-4631-b024-6e8a5bd8c42e", + "alias" : "Reset - Conditional OTP", + "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "conditional-user-configured", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-otp", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "6246dbfb-a619-4635-ab3e-b394f0ed4af3", + "alias" : "User creation or linking", + "description" : "Flow for the existing/non-existing user alternatives", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "create unique user config", + "authenticator" : "idp-create-user-if-unique", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Handle Existing Account", + "userSetupAllowed" : false + } ] + }, { + "id" : "efa63a87-ea5c-4519-a323-11099438f81e", + "alias" : "Verify Existing Account by Re-authentication", + "description" : "Reauthentication of existing account", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "idp-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "First broker login - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "2cd74a88-7aff-42ad-a94e-776089c0aaca", + "alias" : "browser", + "description" : "browser based authentication", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-cookie", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "auth-spnego", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "identity-provider-redirector", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 25, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "forms", + "userSetupAllowed" : false + } ] + }, { + "id" : "f14b6e2e-bf4f-4dc3-906e-8e417cb8a96b", + "alias" : "clients", + "description" : "Base authentication for clients", + "providerId" : "client-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "client-secret", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-secret-jwt", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "client-x509", + "authenticatorFlow" : false, + "requirement" : "ALTERNATIVE", + "priority" : 40, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "704eac4a-0f3f-42b1-8326-ed8193f7c5e0", + "alias" : "direct grant", + "description" : "OpenID Connect Resource Owner Grant", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "direct-grant-validate-username", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "direct-grant-validate-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 30, + "autheticatorFlow" : true, + "flowAlias" : "Direct Grant - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "ea358460-4bdb-4926-8bdb-9ae28b76a73a", + "alias" : "docker auth", + "description" : "Used by Docker clients to authenticate against the IDP", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "docker-http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "064b83a9-eff4-47c0-a885-5a2552a390b7", + "alias" : "first broker login", + "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticatorConfig" : "review profile config", + "authenticator" : "idp-review-profile", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "User creation or linking", + "userSetupAllowed" : false + } ] + }, { + "id" : "224e3374-9c81-437a-9abb-557eba90dcea", + "alias" : "forms", + "description" : "Username, password, otp and other auth forms.", + "providerId" : "basic-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "auth-username-password-form", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 20, + "autheticatorFlow" : true, + "flowAlias" : "Browser - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "ccfe54ac-0522-45a5-8cf5-a3bd89396ea8", + "alias" : "registration", + "description" : "registration flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-page-form", + "authenticatorFlow" : true, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : true, + "flowAlias" : "registration form", + "userSetupAllowed" : false + } ] + }, { + "id" : "09319bf1-dc33-4d5a-9866-20ef01e3364e", + "alias" : "registration form", + "description" : "registration form", + "providerId" : "form-flow", + "topLevel" : false, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "registration-user-creation", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-password-action", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 50, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-recaptcha-action", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 60, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "registration-terms-and-conditions", + "authenticatorFlow" : false, + "requirement" : "DISABLED", + "priority" : 70, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + }, { + "id" : "e55eed94-e227-4edc-af34-5d8595fb2379", + "alias" : "reset credentials", + "description" : "Reset credentials for a user if they forgot their password or something", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "reset-credentials-choose-user", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-credential-email", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 20, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticator" : "reset-password", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 30, + "autheticatorFlow" : false, + "userSetupAllowed" : false + }, { + "authenticatorFlow" : true, + "requirement" : "CONDITIONAL", + "priority" : 40, + "autheticatorFlow" : true, + "flowAlias" : "Reset - Conditional OTP", + "userSetupAllowed" : false + } ] + }, { + "id" : "4ce089cb-31f2-4c4c-99a8-fd7f2c6b458e", + "alias" : "saml ecp", + "description" : "SAML ECP Profile Authentication Flow", + "providerId" : "basic-flow", + "topLevel" : true, + "builtIn" : true, + "authenticationExecutions" : [ { + "authenticator" : "http-basic-authenticator", + "authenticatorFlow" : false, + "requirement" : "REQUIRED", + "priority" : 10, + "autheticatorFlow" : false, + "userSetupAllowed" : false + } ] + } ], + "authenticatorConfig" : [ { + "id" : "ca32fd2e-b807-49d6-aa2a-ece90de7c6d1", + "alias" : "create unique user config", + "config" : { + "require.password.update.after.registration" : "false" } - ], - "authenticatorConfig": [ - { - "id": "ca32fd2e-b807-49d6-aa2a-ece90de7c6d1", - "alias": "create unique user config", - "config": { - "require.password.update.after.registration": "false" - } - }, - { - "id": "87fbbfb1-9fad-4954-85be-84d15d6f8bc6", - "alias": "review profile config", - "config": { - "update.profile.on.first.login": "missing" - } + }, { + "id" : "87fbbfb1-9fad-4954-85be-84d15d6f8bc6", + "alias" : "review profile config", + "config" : { + "update.profile.on.first.login" : "missing" } - ], - "requiredActions": [ - { - "alias": "CONFIGURE_TOTP", - "name": "Configure OTP", - "providerId": "CONFIGURE_TOTP", - "enabled": true, - "defaultAction": false, - "priority": 10, - "config": {} - }, - { - "alias": "TERMS_AND_CONDITIONS", - "name": "Terms and Conditions", - "providerId": "TERMS_AND_CONDITIONS", - "enabled": false, - "defaultAction": false, - "priority": 20, - "config": {} - }, - { - "alias": "UPDATE_PASSWORD", - "name": "Update Password", - "providerId": "UPDATE_PASSWORD", - "enabled": true, - "defaultAction": false, - "priority": 30, - "config": {} - }, - { - "alias": "UPDATE_PROFILE", - "name": "Update Profile", - "providerId": "UPDATE_PROFILE", - "enabled": true, - "defaultAction": false, - "priority": 40, - "config": {} - }, - { - "alias": "VERIFY_EMAIL", - "name": "Verify Email", - "providerId": "VERIFY_EMAIL", - "enabled": true, - "defaultAction": false, - "priority": 50, - "config": {} - }, - { - "alias": "delete_account", - "name": "Delete Account", - "providerId": "delete_account", - "enabled": false, - "defaultAction": false, - "priority": 60, - "config": {} - }, - { - "alias": "webauthn-register", - "name": "Webauthn Register", - "providerId": "webauthn-register", - "enabled": true, - "defaultAction": false, - "priority": 70, - "config": {} - }, - { - "alias": "webauthn-register-passwordless", - "name": "Webauthn Register Passwordless", - "providerId": "webauthn-register-passwordless", - "enabled": true, - "defaultAction": false, - "priority": 80, - "config": {} - }, - { - "alias": "VERIFY_PROFILE", - "name": "Verify Profile", - "providerId": "VERIFY_PROFILE", - "enabled": true, - "defaultAction": false, - "priority": 90, - "config": {} - }, - { - "alias": "delete_credential", - "name": "Delete Credential", - "providerId": "delete_credential", - "enabled": true, - "defaultAction": false, - "priority": 100, - "config": {} - }, - { - "alias": "update_user_locale", - "name": "Update User Locale", - "providerId": "update_user_locale", - "enabled": true, - "defaultAction": false, - "priority": 1000, - "config": {} - } - ], - "browserFlow": "browser", - "registrationFlow": "registration", - "directGrantFlow": "direct grant", - "resetCredentialsFlow": "reset credentials", - "clientAuthenticationFlow": "clients", - "dockerAuthenticationFlow": "docker auth", - "firstBrokerLoginFlow": "first broker login", - "attributes": { - "cibaBackchannelTokenDeliveryMode": "poll", - "cibaExpiresIn": "120", - "cibaAuthRequestedUserHint": "login_hint", - "oauth2DeviceCodeLifespan": "600", - "oauth2DevicePollingInterval": "5", - "parRequestUriLifespan": "60", - "cibaInterval": "5", - "realmReusableOtpCode": "false" + } ], + "requiredActions" : [ { + "alias" : "CONFIGURE_TOTP", + "name" : "Configure OTP", + "providerId" : "CONFIGURE_TOTP", + "enabled" : true, + "defaultAction" : false, + "priority" : 10, + "config" : { } + }, { + "alias" : "TERMS_AND_CONDITIONS", + "name" : "Terms and Conditions", + "providerId" : "TERMS_AND_CONDITIONS", + "enabled" : false, + "defaultAction" : false, + "priority" : 20, + "config" : { } + }, { + "alias" : "UPDATE_PASSWORD", + "name" : "Update Password", + "providerId" : "UPDATE_PASSWORD", + "enabled" : true, + "defaultAction" : false, + "priority" : 30, + "config" : { } + }, { + "alias" : "UPDATE_PROFILE", + "name" : "Update Profile", + "providerId" : "UPDATE_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 40, + "config" : { } + }, { + "alias" : "VERIFY_EMAIL", + "name" : "Verify Email", + "providerId" : "VERIFY_EMAIL", + "enabled" : true, + "defaultAction" : false, + "priority" : 50, + "config" : { } + }, { + "alias" : "delete_account", + "name" : "Delete Account", + "providerId" : "delete_account", + "enabled" : false, + "defaultAction" : false, + "priority" : 60, + "config" : { } + }, { + "alias" : "webauthn-register", + "name" : "Webauthn Register", + "providerId" : "webauthn-register", + "enabled" : true, + "defaultAction" : false, + "priority" : 70, + "config" : { } + }, { + "alias" : "webauthn-register-passwordless", + "name" : "Webauthn Register Passwordless", + "providerId" : "webauthn-register-passwordless", + "enabled" : true, + "defaultAction" : false, + "priority" : 80, + "config" : { } + }, { + "alias" : "VERIFY_PROFILE", + "name" : "Verify Profile", + "providerId" : "VERIFY_PROFILE", + "enabled" : true, + "defaultAction" : false, + "priority" : 90, + "config" : { } + }, { + "alias" : "delete_credential", + "name" : "Delete Credential", + "providerId" : "delete_credential", + "enabled" : true, + "defaultAction" : false, + "priority" : 100, + "config" : { } + }, { + "alias" : "update_user_locale", + "name" : "Update User Locale", + "providerId" : "update_user_locale", + "enabled" : true, + "defaultAction" : false, + "priority" : 1000, + "config" : { } + } ], + "browserFlow" : "browser", + "registrationFlow" : "registration", + "directGrantFlow" : "direct grant", + "resetCredentialsFlow" : "reset credentials", + "clientAuthenticationFlow" : "clients", + "dockerAuthenticationFlow" : "docker auth", + "firstBrokerLoginFlow" : "first broker login", + "attributes" : { + "cibaBackchannelTokenDeliveryMode" : "poll", + "cibaAuthRequestedUserHint" : "login_hint", + "clientOfflineSessionMaxLifespan" : "0", + "oauth2DevicePollingInterval" : "5", + "clientSessionIdleTimeout" : "0", + "clientOfflineSessionIdleTimeout" : "0", + "cibaInterval" : "5", + "realmReusableOtpCode" : "false", + "cibaExpiresIn" : "120", + "oauth2DeviceCodeLifespan" : "600", + "parRequestUriLifespan" : "60", + "clientSessionMaxLifespan" : "0", + "organizationsEnabled" : "false" }, - "keycloakVersion": "25.0.2", - "userManagedAccessAllowed": false, - "organizationsEnabled": false, - "clientProfiles": { - "profiles": [] + "keycloakVersion" : "25.0.2", + "userManagedAccessAllowed" : false, + "organizationsEnabled" : false, + "clientProfiles" : { + "profiles" : [ ] }, - "clientPolicies": { - "policies": [] + "clientPolicies" : { + "policies" : [ ] } } \ No newline at end of file -- 2.49.1 From 5c532a6bb53f3a7f2d5fa82b0dbe51f478425325 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 17 Aug 2024 16:38:41 +0200 Subject: [PATCH 011/150] Experiment with setting up system test docker services --- src/WebApi/Authentication/JwtOptions.cs | 2 + .../Common/DependencyInjectionExtensions.cs | 10 +- src/WebApi/Common/StartupExtensions.cs | 2 +- tests/WebApi.Tests.System/ComposeService.cs | 166 ++++++++++ tests/WebApi.Tests.System/Constants.cs | 13 + .../DockerComposeFixture.cs | 301 ++++++++++++++++++ .../SharedTestCollection.cs | 7 + .../WebApi.Tests.System/SharedTestContext.cs | 11 + tests/WebApi.Tests.System/Test.cs | 57 ++++ tests/WebApi.Tests.System/TokenResponse.cs | 9 + .../WebApi.Tests.System.csproj | 1 + tests/WebApi.Tests.System/compose.system.yaml | 13 +- tests/WebApi.Tests.System/test-realm.json | 11 +- 13 files changed, 590 insertions(+), 13 deletions(-) create mode 100644 tests/WebApi.Tests.System/ComposeService.cs create mode 100644 tests/WebApi.Tests.System/Constants.cs create mode 100644 tests/WebApi.Tests.System/DockerComposeFixture.cs create mode 100644 tests/WebApi.Tests.System/SharedTestCollection.cs create mode 100644 tests/WebApi.Tests.System/SharedTestContext.cs create mode 100644 tests/WebApi.Tests.System/Test.cs create mode 100644 tests/WebApi.Tests.System/TokenResponse.cs diff --git a/src/WebApi/Authentication/JwtOptions.cs b/src/WebApi/Authentication/JwtOptions.cs index 01e8223..ae44dbe 100644 --- a/src/WebApi/Authentication/JwtOptions.cs +++ b/src/WebApi/Authentication/JwtOptions.cs @@ -11,6 +11,8 @@ public class JwtOptions public string MetadataUrl { get; set; } = ""; public string? NameClaimType { get; set; } + + public bool AllowHttpMetadataUrl { get; set; } } public class JwtOptionsValidator : AbstractValidator diff --git a/src/WebApi/Common/DependencyInjectionExtensions.cs b/src/WebApi/Common/DependencyInjectionExtensions.cs index bab1a0d..7e09813 100644 --- a/src/WebApi/Common/DependencyInjectionExtensions.cs +++ b/src/WebApi/Common/DependencyInjectionExtensions.cs @@ -18,14 +18,16 @@ public static class DependencyInjectionExtensions /// Adds all the WebApi related services to the Dependency Injection container. /// /// - public static void AddWebApiServices(this IServiceCollection services, IConfiguration configuration) + /// + /// + public static void AddWebApiServices(this IServiceCollection services, IConfiguration configuration, IHostEnvironment environment) { services .AddMiscellaneousServices() .AddOpenApi() .AddApiVersioning() .AddOtel() - .AddAuthenticationAndAuthorization() + .AddAuthenticationAndAuthorization(environment) .AddDbContext(configuration); } @@ -113,7 +115,7 @@ public static class DependencyInjectionExtensions return services; } - private static IServiceCollection AddAuthenticationAndAuthorization(this IServiceCollection services) + private static IServiceCollection AddAuthenticationAndAuthorization(this IServiceCollection services, IHostEnvironment environment) { services.AddOptions() .BindConfiguration(JwtOptions.SectionName) @@ -134,6 +136,8 @@ public static class DependencyInjectionExtensions { o.TokenValidationParameters.NameClaimType = jwtOptions.Value.NameClaimType; } + + o.RequireHttpsMetadata = !jwtOptions.Value.AllowHttpMetadataUrl && !environment.IsDevelopment(); }); services.AddAuthorizationBuilder() diff --git a/src/WebApi/Common/StartupExtensions.cs b/src/WebApi/Common/StartupExtensions.cs index 4b6b07d..351875f 100644 --- a/src/WebApi/Common/StartupExtensions.cs +++ b/src/WebApi/Common/StartupExtensions.cs @@ -11,7 +11,7 @@ internal static class StartupExtensions { builder.Configuration.AddEnvironmentVariables("Vegasco_"); - builder.Services.AddWebApiServices(builder.Configuration); + builder.Services.AddWebApiServices(builder.Configuration, builder.Environment); WebApplication app = builder.Build(); return app; diff --git a/tests/WebApi.Tests.System/ComposeService.cs b/tests/WebApi.Tests.System/ComposeService.cs new file mode 100644 index 0000000..05483c4 --- /dev/null +++ b/tests/WebApi.Tests.System/ComposeService.cs @@ -0,0 +1,166 @@ +using Ductus.FluentDocker.Services; +using Ductus.FluentDocker.Services.Extensions; +using System.Diagnostics.CodeAnalysis; + +namespace WebApi.Tests.System; + +public abstract class ComposeService : IDisposable +{ + public string ServiceName { get; init; } + public string ServiceInternalPort { get; init; } + public string ServiceInternalProtocol { get; init; } + public string ServiceInternalPortAndProtocol => $"{ServiceInternalPort}/{ServiceInternalProtocol}"; + + private readonly ICompositeService _dockerService; + private readonly bool _isTestRunningInContainer; + + private IContainerService? _container; + private bool _hasCheckedForContainer; + + /// + /// Not null, if is true. + /// + public IContainerService? Container + { + get + { + if (_hasCheckedForContainer) + { + return _container; + } + + _container ??= _dockerService.Containers.First(x => x.Name == ServiceName); + _hasCheckedForContainer = true; + + return _container; + } + } + + [MemberNotNullWhen(returnValue: true, nameof(Container))] + public bool ContainerExists => Container is not null; + + public ComposeService( + ICompositeService dockerService, + bool isTestRunningInContainer, + string serviceName, + string serviceInternalPort, + string serviceInternalProtocol = "tcp") + { + _dockerService = dockerService; + _isTestRunningInContainer = isTestRunningInContainer; + ServiceName = serviceName; + ServiceInternalPort = serviceInternalPort; + ServiceInternalProtocol = serviceInternalProtocol; + } + + public string? GetServiceUrl() + { + if (!ContainerExists) + { + return null; + } + + return _isTestRunningInContainer + ? GetServiceUrlWhenRunningInsideContainer() + : GetUrlFromOutsideContainer(Container, ServiceInternalPortAndProtocol); + } + + private string GetServiceUrlWhenRunningInsideContainer() + { + return $"http://{ServiceName}:{ServiceInternalPort}"; + } + + private static string GetUrlFromOutsideContainer(IContainerService container, string portAndProto) + { + var ipEndpoint = container.ToHostExposedEndpoint(portAndProto); + return $"http://{ipEndpoint.Address}:{ipEndpoint.Port}"; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _container?.Dispose(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } +} + +public sealed class AppContainer : ComposeService +{ + public AppContainer(ICompositeService dockerService, bool isTestRunningInContainer) + : base(dockerService, isTestRunningInContainer, "app", "8080") + { + } +} + +public sealed class LoginContainer : ComposeService +{ + public LoginContainer(ICompositeService dockerService, bool isTestRunningInContainer) + : base(dockerService, isTestRunningInContainer, "login", "8080") + { + } +} + + +public sealed class TestAppContainer : IDisposable +{ + private IContainerService? _testApplicationContainer; + private bool _hasCheckedForThisContainer; + public IContainerService? Container + { + get + { + if (!_hasCheckedForThisContainer) + { + _testApplicationContainer = _dockerHost.GetRunningContainers() + .FirstOrDefault(x => x.Id.StartsWith(Environment.MachineName)); + + if (_testApplicationContainer is not null) + { + // If the test is running inside a container (i.e. usually in a pipeline), we do not want to mess with the container, just release the resources held by this program + _testApplicationContainer.RemoveOnDispose = false; + _testApplicationContainer.StopOnDispose = false; + } + + _hasCheckedForThisContainer = true; + } + + return _testApplicationContainer; + } + } + + private bool _hasCheckedIfTestRunInContainer; + private bool _isTestRunningInContainer; + public bool IsTestRunningInContainer + { + get + { + if (!_hasCheckedIfTestRunInContainer) + { + _isTestRunningInContainer = Container is not null; + _hasCheckedIfTestRunInContainer = true; + } + + return _isTestRunningInContainer; + } + } + + + private readonly IHostService _dockerHost; + + public TestAppContainer(IHostService dockerHost) + { + _dockerHost = dockerHost; + } + + public void Dispose() + { + _testApplicationContainer?.Dispose(); + } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.System/Constants.cs b/tests/WebApi.Tests.System/Constants.cs new file mode 100644 index 0000000..4f05ff6 --- /dev/null +++ b/tests/WebApi.Tests.System/Constants.cs @@ -0,0 +1,13 @@ +namespace WebApi.Tests.System; + +public static class Constants +{ + public static class Login + { + public const string ClientId = "vegasco"; + public const string ClientSecret = "siIgnkijkkIxeQ9BDNwnGGUb60S53QZh"; + public const string Username = "test.user"; + public const string Password = "T3sttest."; + public const string Realm = "development"; + } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.System/DockerComposeFixture.cs b/tests/WebApi.Tests.System/DockerComposeFixture.cs new file mode 100644 index 0000000..8922f73 --- /dev/null +++ b/tests/WebApi.Tests.System/DockerComposeFixture.cs @@ -0,0 +1,301 @@ +using Ductus.FluentDocker.Builders; +using Ductus.FluentDocker.Extensions; +using Ductus.FluentDocker.Model.Common; +using Ductus.FluentDocker.Model.Compose; +using Ductus.FluentDocker.Services; +using Ductus.FluentDocker.Services.Extensions; + +namespace WebApi.Tests.System; + +public sealed class DockerComposeFixture : IDisposable +{ + private const string ComposeFileName = "compose.system.yaml"; + + private const string AppServiceName = "app"; + private const string AppServiceInternalPort = "8080"; + private const string AppServiceInternalPortAndProtocol = $"{AppServiceInternalPort}/tcp"; + + private const string LoginServiceName = "login"; + private const string LoginServiceInternalPort = "8080"; + private const string LoginServiceInternalPortAndProtocol = $"{LoginServiceInternalPort}/tcp"; + + private static readonly string ComposeFilePath = Path.GetFullPath(Path.Combine("../../..", ComposeFileName)); + + private readonly ICompositeService _dockerService; + + + private bool _hasCheckedForThisContainer; + private bool _hasCheckedIfTestRunInContainer; + + private IHostService? _host; + + private bool _isTestRunningInContainer; + private INetworkService? _networkService; + private IContainerService? _testApplicationContainer; + + public DockerComposeFixture() + { + _dockerService = GetDockerComposeServices(); + _dockerService.Start(); + AttachDockerNetworksIfRunningInContainer(); + } + + private IContainerService? _appContainer; + public IContainerService AppContainer + { + get + { + _appContainer ??= _dockerService.Containers.First(x => x.Name == AppServiceName); + return _appContainer; + } + } + + private IContainerService? _loginContainer; + public IContainerService LoginContainer + { + get + { + _loginContainer ??= _dockerService.Containers.First(x => x.Name == LoginServiceName); + return _loginContainer; + } + } + + public IContainerService? TestApplicationContainer + { + get + { + if (!_hasCheckedForThisContainer) + { + _testApplicationContainer = DockerHost.GetRunningContainers() + .FirstOrDefault(x => x.Id.StartsWith(Environment.MachineName)); + + if (_testApplicationContainer is not null) + { + // If the test is running inside a container (i.e. usually in a pipeline), we do not want to mess with the container, just release the resources held by this program + _testApplicationContainer.RemoveOnDispose = false; + _testApplicationContainer.StopOnDispose = false; + } + + _hasCheckedForThisContainer = true; + } + + return _testApplicationContainer; + } + } + + public IHostService DockerHost + { + get + { + var hosts = new Hosts().Discover(); + _host = hosts.FirstOrDefault(x => x.IsNative) ?? + hosts.FirstOrDefault(x => x.Name == "default") ?? + hosts.FirstOrDefault(); + + if (_host is null) throw new InvalidOperationException("No docker host found"); + + return _host; + } + } + + public bool IsTestRunningInContainer + { + get + { + if (!_hasCheckedIfTestRunInContainer) + { + _isTestRunningInContainer = TestApplicationContainer is not null; + _hasCheckedIfTestRunInContainer = true; + } + + return _isTestRunningInContainer; + } + } + + public void Dispose() + { + _networkService?.Dispose(); + + _testApplicationContainer?.Dispose(); + _appContainer?.Dispose(); + _loginContainer?.Dispose(); + + // Kill container because otherwise the _dockerService.Dispose() takes much longer + KillDockerComposeServices(); + + _dockerService.Dispose(); + + _host?.Dispose(); + } + + private ICompositeService GetDockerComposeServices() + { + var services = new Builder() + .UseContainer() + .UseCompose() + .AssumeComposeVersion(ComposeVersion.V2) + .FromFile((TemplateString)ComposeFilePath) + .ForceBuild() + .RemoveOrphans() + .Wait("app", WaitForApplicationToListenToRequests) + .Build(); + + return services; + } + + private int WaitForApplicationToListenToRequests(IContainerService container, int iteration) + { + const int maxTryCount = 15; + ArgumentOutOfRangeException.ThrowIfGreaterThan(iteration, maxTryCount); + + var isStarted = container.Logs().ReadToEnd().Reverse().Any(x => x.Contains("Now listening on:")); + return isStarted ? 0 : 500; + } + + private void AttachDockerNetworksIfRunningInContainer() + { + if (!IsTestRunningInContainer) return; + + var randomNetworkName = Guid.NewGuid().ToString("N"); + _networkService = DockerHost.CreateNetwork(randomNetworkName, removeOnDispose: true); + + _networkService.Attach(AppContainer, true, AppServiceName); + _networkService.Attach(TestApplicationContainer, true); + } + + public string GetAppUrl() + { + return IsTestRunningInContainer + ? GetAppUrlWhenRunningInsideContainer() + : GetUrlFromOutsideContainer(AppContainer, AppServiceInternalPortAndProtocol); + } + + private static string GetAppUrlWhenRunningInsideContainer() + { + return $"http://{AppServiceName}:{AppServiceInternalPort}"; + } + + public string GetLoginUrl() + { + return IsTestRunningInContainer + ? GetLoginUrlWhenRunningInsideContainer() + : GetUrlFromOutsideContainer(LoginContainer, LoginServiceInternalPortAndProtocol); + } + + private static string GetLoginUrlWhenRunningInsideContainer() + { + return $"http://{LoginServiceName}:{LoginServiceInternalPort}"; + } + + private static string GetUrlFromOutsideContainer(IContainerService container, string portAndProto) + { + var ipEndpoint = container.ToHostExposedEndpoint(portAndProto); + return $"http://{ipEndpoint.Address}:{ipEndpoint.Port}"; + } + + private void KillDockerComposeServices() + { + foreach (var container in _dockerService.Containers) container.Remove(true); + } +} + + +public sealed class DockerComposeFixture2 : IDisposable +{ + private const string ComposeFileName = "compose.system.yaml"; + + private static readonly string ComposeFilePath = Path.GetFullPath(Path.Combine("../../..", ComposeFileName)); + + private readonly ICompositeService _dockerService; + + private IHostService? _host; + + private INetworkService? _networkService; + + public AppContainer AppContainer { get; init; } + public LoginContainer LoginContainer { get; init; } + public TestAppContainer TestApplicationContainer { get; init; } + + public DockerComposeFixture2() + { + _dockerService = GetDockerComposeServices(); + _dockerService.Start(); + + TestApplicationContainer = new TestAppContainer(DockerHost); + AppContainer = new AppContainer(_dockerService, TestApplicationContainer.IsTestRunningInContainer); + LoginContainer = new LoginContainer(_dockerService, TestApplicationContainer.IsTestRunningInContainer); + + AttachDockerNetworksIfRunningInContainer(); + } + + public IHostService DockerHost + { + get + { + var hosts = new Hosts().Discover(); + _host = hosts.FirstOrDefault(x => x.IsNative) ?? + hosts.FirstOrDefault(x => x.Name == "default") ?? + hosts.FirstOrDefault(); + + if (_host is null) throw new InvalidOperationException("No docker host found"); + + return _host; + } + } + + public void Dispose() + { + _networkService?.Dispose(); + + TestApplicationContainer.Dispose(); + AppContainer.Dispose(); + LoginContainer.Dispose(); + + // Kill container because otherwise the _dockerService.Dispose() takes much longer + KillDockerComposeServices(); + + _dockerService.Dispose(); + + _host?.Dispose(); + } + + private ICompositeService GetDockerComposeServices() + { + var services = new Builder() + .UseContainer() + .UseCompose() + .AssumeComposeVersion(ComposeVersion.V2) + .FromFile((TemplateString)ComposeFilePath) + .ForceBuild() + .RemoveOrphans() + .Wait("app", WaitForApplicationToListenToRequests) + .Build(); + + return services; + } + + private int WaitForApplicationToListenToRequests(IContainerService container, int iteration) + { + const int maxTryCount = 15; + ArgumentOutOfRangeException.ThrowIfGreaterThan(iteration, maxTryCount); + + var isStarted = container.Logs().ReadToEnd().Reverse().Any(x => x.Contains("Now listening on:")); + return isStarted ? 0 : 500; + } + + private void AttachDockerNetworksIfRunningInContainer() + { + if (!TestApplicationContainer.IsTestRunningInContainer) return; + + var randomNetworkName = Guid.NewGuid().ToString("N"); + _networkService = DockerHost.CreateNetwork(randomNetworkName, removeOnDispose: true); + + _networkService.Attach(AppContainer.Container!, true, AppContainer.ServiceName); + _networkService.Attach(TestApplicationContainer.Container, true); + } + + private void KillDockerComposeServices() + { + foreach (var container in _dockerService.Containers) container.Remove(true); + } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.System/SharedTestCollection.cs b/tests/WebApi.Tests.System/SharedTestCollection.cs new file mode 100644 index 0000000..94787b7 --- /dev/null +++ b/tests/WebApi.Tests.System/SharedTestCollection.cs @@ -0,0 +1,7 @@ +namespace WebApi.Tests.System; + +[CollectionDefinition(Name)] +public class SharedTestCollection : ICollectionFixture +{ + public const string Name = nameof(SharedTestCollection); +} diff --git a/tests/WebApi.Tests.System/SharedTestContext.cs b/tests/WebApi.Tests.System/SharedTestContext.cs new file mode 100644 index 0000000..aca0b6d --- /dev/null +++ b/tests/WebApi.Tests.System/SharedTestContext.cs @@ -0,0 +1,11 @@ +namespace WebApi.Tests.System; + +public sealed class SharedTestContext : IDisposable +{ + public DockerComposeFixture2 DockerComposeFixture { get; set; } = new(); + + public void Dispose() + { + DockerComposeFixture.Dispose(); + } +} diff --git a/tests/WebApi.Tests.System/Test.cs b/tests/WebApi.Tests.System/Test.cs new file mode 100644 index 0000000..38aaf15 --- /dev/null +++ b/tests/WebApi.Tests.System/Test.cs @@ -0,0 +1,57 @@ +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; + +namespace WebApi.Tests.System; + +[Collection(SharedTestCollection.Name)] +public class Test +{ + private readonly SharedTestContext _context; + + public Test(SharedTestContext context) + { + _context = context; + } + + //[Fact] + public async Task Test1() + { + var loginUrl = _context.DockerComposeFixture.LoginContainer.GetServiceUrl(); + var baseUrl = new Uri(loginUrl!, UriKind.Absolute); + var relativeUrl = new Uri($"/realms/{Constants.Login.Realm}/protocol/openid-connect/token", UriKind.Relative); + var uri = new Uri(baseUrl, relativeUrl); + var request = new HttpRequestMessage(HttpMethod.Post, uri); + + var data = new Dictionary + { + { "grant_type", "password" }, + { "audience", Constants.Login.ClientId }, + { "username", Constants.Login.Username }, + { "password", Constants.Login.Password } + }; + request.Content = new FormUrlEncodedContent(data); + + request.Headers.Authorization = new AuthenticationHeaderValue("Basic", + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Constants.Login.ClientId}:{Constants.Login.ClientSecret}"))); + + using var client = new HttpClient(); + using var response = await client.SendAsync(request); + + var content = await response.Content.ReadAsStringAsync(); + var tokenResponse = JsonSerializer.Deserialize(content); + + var appUrl = _context.DockerComposeFixture.AppContainer.GetServiceUrl(); + baseUrl = new Uri(appUrl!, UriKind.Absolute); + relativeUrl = new Uri("/v1/cars", UriKind.Relative); + uri = new Uri(baseUrl, relativeUrl); + + request = new HttpRequestMessage(HttpMethod.Get, uri); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse!.AccessToken); + + using var response2 = await client.SendAsync(request); + + var content2 = await response2.Content.ReadAsStringAsync(); + + } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.System/TokenResponse.cs b/tests/WebApi.Tests.System/TokenResponse.cs new file mode 100644 index 0000000..58b157a --- /dev/null +++ b/tests/WebApi.Tests.System/TokenResponse.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace WebApi.Tests.System; + +public class TokenResponse +{ + [JsonPropertyName("access_token")] + public required string AccessToken { get; init; } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.System/WebApi.Tests.System.csproj b/tests/WebApi.Tests.System/WebApi.Tests.System.csproj index 9c5b30a..2e0f217 100644 --- a/tests/WebApi.Tests.System/WebApi.Tests.System.csproj +++ b/tests/WebApi.Tests.System/WebApi.Tests.System.csproj @@ -11,6 +11,7 @@ + diff --git a/tests/WebApi.Tests.System/compose.system.yaml b/tests/WebApi.Tests.System/compose.system.yaml index 4df77c0..9c15fa0 100644 --- a/tests/WebApi.Tests.System/compose.system.yaml +++ b/tests/WebApi.Tests.System/compose.system.yaml @@ -3,9 +3,12 @@ services: build: ../../src/WebApi environment: Vegasco_ConnectionStrings__Default: "Host=db;Port=5432;Database=postgres;Username=postgres;Password=postgres" - Vegasco_JWT__Issuer: - Vegasco_JWT__Authority: - Vegasco_JWT__Audience: + Vegasco_JWT__MetadataUrl: http://login:8080/realms/development/.well-known/openid-configuration + Vegasco_JWT__ValidAudience: vegasco + Vegasco_JWT__NameClaimType: name + Vegasco_JWT__AllowHttpMetadataUrl: "true" + ports: + - "8080" depends_on: db: condition: service_healthy @@ -38,12 +41,12 @@ services: KC_DB_PASSWORD: keycloak KEYCLOAK_ADMIN: admin KEYCLOAK_ADMIN_PASSWORD: admin1! - KC_HOSTNAME: http://localhost:12345/ + KC_HOSTNAME_STRICT: false KC_HEALTH_ENABLED: true KC_METRICS_ENABLED: true KC_HTTP_ENABLED: true ports: - - 12345:8080 + - "8080" volumes: - ./test-realm.json:/opt/keycloak/data/import/test-realm.json:ro depends_on: diff --git a/tests/WebApi.Tests.System/test-realm.json b/tests/WebApi.Tests.System/test-realm.json index d654156..46481f5 100644 --- a/tests/WebApi.Tests.System/test-realm.json +++ b/tests/WebApi.Tests.System/test-realm.json @@ -667,6 +667,7 @@ "enabled" : true, "alwaysDisplayInConsole" : false, "clientAuthenticatorType" : "client-secret", + "secret" : "siIgnkijkkIxeQ9BDNwnGGUb60S53QZh", "redirectUris" : [ "*" ], "webOrigins" : [ ], "notBefore" : 0, @@ -674,15 +675,17 @@ "consentRequired" : false, "standardFlowEnabled" : true, "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, + "directAccessGrantsEnabled" : true, "serviceAccountsEnabled" : false, - "publicClient" : true, + "publicClient" : false, "frontchannelLogout" : true, "protocol" : "openid-connect", "attributes" : { "oidc.ciba.grant.enabled" : "false", + "client.secret.creation.time" : "1723219692", "backchannel.logout.session.required" : "true", "post.logout.redirect.uris" : "*", + "display.on.consent.screen" : "false", "oauth2.device.authorization.grant.enabled" : "false", "backchannel.logout.revoke.offline.tokens" : "false" }, @@ -1279,7 +1282,7 @@ "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "saml-user-property-mapper", "oidc-address-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-sha256-pairwise-sub-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "saml-user-property-mapper" ] } }, { "id" : "b099d087-5954-460d-902f-def7799cb005", @@ -1297,7 +1300,7 @@ "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "saml-role-list-mapper", "saml-user-property-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "saml-user-property-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper" ] } }, { "id" : "e86226d6-0944-4c08-b809-d72e6c7991c4", -- 2.49.1 From 4bfc57ef9f2279728318aacf2e543d4e51776844 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 17 Aug 2024 16:38:41 +0200 Subject: [PATCH 012/150] Remove unused launch configs --- src/WebApi/Properties/launchSettings.json | 39 +---------------------- 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/src/WebApi/Properties/launchSettings.json b/src/WebApi/Properties/launchSettings.json index a2d8742..0dec4f4 100644 --- a/src/WebApi/Properties/launchSettings.json +++ b/src/WebApi/Properties/launchSettings.json @@ -1,15 +1,5 @@ { "profiles": { - "http": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "dotnetRunMessages": true, - "applicationUrl": "http://localhost:5076" - }, "https": { "commandName": "Project", "launchBrowser": true, @@ -19,34 +9,7 @@ }, "dotnetRunMessages": true, "applicationUrl": "https://localhost:7098;http://localhost:5076" - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Container (Dockerfile)": { - "commandName": "Docker", - "launchBrowser": true, - "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", - "environmentVariables": { - "ASPNETCORE_HTTPS_PORTS": "8081", - "ASPNETCORE_HTTP_PORTS": "8080" - }, - "publishAllPorts": true, - "useSSL": true } }, - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:22111", - "sslPort": 44373 - } - } + "$schema": "http://json.schemastore.org/launchsettings.json" } \ No newline at end of file -- 2.49.1 From d47e4c1971c472a6b0dbd8f1895c603953313a6d Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 17 Aug 2024 18:00:23 +0200 Subject: [PATCH 013/150] Add consumption entity and use strongly typed ids --- migrations/migration.sql | 33 ++++++++++-- src/WebApi/Assembly.cs | 3 ++ src/WebApi/Cars/Car.cs | 8 ++- src/WebApi/Cars/CarId.cs | 6 +++ src/WebApi/Cars/CreateCar.cs | 2 +- src/WebApi/Cars/DeleteCar.cs | 2 +- src/WebApi/Cars/GetCar.cs | 4 +- src/WebApi/Cars/GetCars.cs | 2 +- src/WebApi/Cars/UpdateCar.cs | 4 +- src/WebApi/Consumptions/Consumption.cs | 52 +++++++++++++++++++ src/WebApi/Consumptions/ConsumptionId.cs | 7 +++ ....cs => 20240817153531_Initial.Designer.cs} | 50 ++++++++++++++++-- ...sAndUsers.cs => 20240817153531_Initial.cs} | 32 +++++++++++- .../ApplicationDbContextModelSnapshot.cs | 46 +++++++++++++++- src/WebApi/WebApi.csproj | 10 ++-- .../Cars/CreateCarTests.cs | 3 +- .../Cars/UpdateCarTests.cs | 7 ++- .../WebApi.Tests.Integration.csproj | 16 ++++-- .../WebApi.Tests.System.csproj | 14 +++-- 19 files changed, 265 insertions(+), 36 deletions(-) create mode 100644 src/WebApi/Assembly.cs create mode 100644 src/WebApi/Cars/CarId.cs create mode 100644 src/WebApi/Consumptions/Consumption.cs create mode 100644 src/WebApi/Consumptions/ConsumptionId.cs rename src/WebApi/Persistence/Migrations/{20240803173803_AddCarsAndUsers.Designer.cs => 20240817153531_Initial.Designer.cs} (59%) rename src/WebApi/Persistence/Migrations/{20240803173803_AddCarsAndUsers.cs => 20240817153531_Initial.cs} (57%) diff --git a/migrations/migration.sql b/migrations/migration.sql index f4ef165..67a66e6 100644 --- a/migrations/migration.sql +++ b/migrations/migration.sql @@ -9,7 +9,7 @@ START TRANSACTION; DO $EF$ BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240803173803_AddCarsAndUsers') THEN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN CREATE TABLE "Users" ( "Id" text NOT NULL, CONSTRAINT "PK_Users" PRIMARY KEY ("Id") @@ -19,7 +19,7 @@ END $EF$; DO $EF$ BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240803173803_AddCarsAndUsers') THEN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN CREATE TABLE "Cars" ( "Id" uuid NOT NULL, "Name" character varying(50) NOT NULL, @@ -32,16 +32,39 @@ END $EF$; DO $EF$ BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240803173803_AddCarsAndUsers') THEN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN + CREATE TABLE "Consumption" ( + "Id" uuid NOT NULL, + "DateTime" timestamp with time zone NOT NULL, + "Distance" double precision NOT NULL, + "Amount" double precision NOT NULL, + "IgnoreInCalculation" boolean NOT NULL, + "CarId" uuid NOT NULL, + CONSTRAINT "PK_Consumption" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Consumption_Cars_CarId" FOREIGN KEY ("CarId") REFERENCES "Cars" ("Id") ON DELETE CASCADE + ); + END IF; +END $EF$; + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN CREATE INDEX "IX_Cars_UserId" ON "Cars" ("UserId"); END IF; END $EF$; DO $EF$ BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240803173803_AddCarsAndUsers') THEN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN + CREATE INDEX "IX_Consumption_CarId" ON "Consumption" ("CarId"); + END IF; +END $EF$; + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") - VALUES ('20240803173803_AddCarsAndUsers', '8.0.7'); + VALUES ('20240817153531_Initial', '8.0.8'); END IF; END $EF$; COMMIT; diff --git a/src/WebApi/Assembly.cs b/src/WebApi/Assembly.cs new file mode 100644 index 0000000..d66756a --- /dev/null +++ b/src/WebApi/Assembly.cs @@ -0,0 +1,3 @@ +using StronglyTypedIds; + +[assembly: StronglyTypedIdDefaults(Template.Guid, "guid-efcore")] \ No newline at end of file diff --git a/src/WebApi/Cars/Car.cs b/src/WebApi/Cars/Car.cs index da27e1d..6e72bc4 100644 --- a/src/WebApi/Cars/Car.cs +++ b/src/WebApi/Cars/Car.cs @@ -1,18 +1,21 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Vegasco.WebApi.Consumptions; using Vegasco.WebApi.Users; namespace Vegasco.WebApi.Cars; public class Car { - public Guid Id { get; set; } = Guid.NewGuid(); + public CarId Id { get; set; } = CarId.New(); public string Name { get; set; } = ""; public string UserId { get; set; } = ""; public virtual User User { get; set; } = null!; + + public virtual ICollection Consumptions { get; set; } = []; } public class CarTableConfiguration : IEntityTypeConfiguration @@ -23,6 +26,9 @@ public class CarTableConfiguration : IEntityTypeConfiguration { builder.HasKey(x => x.Id); + builder.Property(x => x.Id) + .HasConversion(); + builder.Property(x => x.Name) .IsRequired() .HasMaxLength(NameMaxLength); diff --git a/src/WebApi/Cars/CarId.cs b/src/WebApi/Cars/CarId.cs new file mode 100644 index 0000000..f901160 --- /dev/null +++ b/src/WebApi/Cars/CarId.cs @@ -0,0 +1,6 @@ +using StronglyTypedIds; + +namespace Vegasco.WebApi.Cars; + +[StronglyTypedId] +public partial struct CarId; diff --git a/src/WebApi/Cars/CreateCar.cs b/src/WebApi/Cars/CreateCar.cs index 2bbbb74..ebcb7e2 100644 --- a/src/WebApi/Cars/CreateCar.cs +++ b/src/WebApi/Cars/CreateCar.cs @@ -63,7 +63,7 @@ public static class CreateCar await dbContext.Cars.AddAsync(car, cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); - Response response = new(car.Id, car.Name); + Response response = new(car.Id.Value, car.Name); return TypedResults.Created($"/v1/cars/{car.Id}", response); } } diff --git a/src/WebApi/Cars/DeleteCar.cs b/src/WebApi/Cars/DeleteCar.cs index 9febdd8..05048d7 100644 --- a/src/WebApi/Cars/DeleteCar.cs +++ b/src/WebApi/Cars/DeleteCar.cs @@ -16,7 +16,7 @@ public static class DeleteCar ApplicationDbContext dbContext, CancellationToken cancellationToken) { - var car = await dbContext.Cars.FindAsync([id], cancellationToken: cancellationToken); + var car = await dbContext.Cars.FindAsync([new CarId(id)], cancellationToken: cancellationToken); if (car is null) { diff --git a/src/WebApi/Cars/GetCar.cs b/src/WebApi/Cars/GetCar.cs index ef954ef..14018a6 100644 --- a/src/WebApi/Cars/GetCar.cs +++ b/src/WebApi/Cars/GetCar.cs @@ -18,14 +18,14 @@ public static class GetCar ApplicationDbContext dbContext, CancellationToken cancellationToken) { - var car = await dbContext.Cars.FindAsync([id], cancellationToken: cancellationToken); + var car = await dbContext.Cars.FindAsync([new CarId(id)], cancellationToken: cancellationToken); if (car is null) { return TypedResults.NotFound(); } - var response = new Response(car.Id, car.Name); + var response = new Response(car.Id.Value, car.Name); return TypedResults.Ok(response); } } diff --git a/src/WebApi/Cars/GetCars.cs b/src/WebApi/Cars/GetCars.cs index 027b026..4bbeb1a 100644 --- a/src/WebApi/Cars/GetCars.cs +++ b/src/WebApi/Cars/GetCars.cs @@ -19,7 +19,7 @@ public static class GetCars CancellationToken cancellationToken) { var cars = await dbContext.Cars - .Select(x => new Response(x.Id, x.Name)) + .Select(x => new Response(x.Id.Value, x.Name)) .ToListAsync(cancellationToken); return TypedResults.Ok(cars); diff --git a/src/WebApi/Cars/UpdateCar.cs b/src/WebApi/Cars/UpdateCar.cs index 6d5ae6b..38b27a9 100644 --- a/src/WebApi/Cars/UpdateCar.cs +++ b/src/WebApi/Cars/UpdateCar.cs @@ -42,7 +42,7 @@ public static class UpdateCar return TypedResults.BadRequest(new HttpValidationProblemDetails(failedValidations.ToCombinedDictionary())); } - var car = await dbContext.Cars.FindAsync([id], cancellationToken: cancellationToken); + Car? car = await dbContext.Cars.FindAsync([new CarId(id)], cancellationToken: cancellationToken); if (car is null) { @@ -52,7 +52,7 @@ public static class UpdateCar car.Name = request.Name; await dbContext.SaveChangesAsync(cancellationToken); - Response response = new(car.Id, car.Name); + Response response = new(car.Id.Value, car.Name); return TypedResults.Ok(response); } } diff --git a/src/WebApi/Consumptions/Consumption.cs b/src/WebApi/Consumptions/Consumption.cs new file mode 100644 index 0000000..3476e31 --- /dev/null +++ b/src/WebApi/Consumptions/Consumption.cs @@ -0,0 +1,52 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Vegasco.WebApi.Cars; + +namespace Vegasco.WebApi.Consumptions; + +public class Consumption +{ + public ConsumptionId Id { get; set; } = ConsumptionId.New(); + + public DateTimeOffset DateTime { get; set; } + + public double Distance { get; set; } + + public double Amount { get; set; } + + public bool IgnoreInCalculation { get; set; } + + public CarId CarId { get; set; } + + public virtual Car Car { get; set; } = null!; +} + +public class ConsumptionTableConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(x => x.Id); + + builder.Property(x => x.Id) + .HasConversion(); + + builder.Property(x => x.DateTime) + .IsRequired(); + + builder.Property(x => x.Distance) + .IsRequired(); + + builder.Property(x => x.Amount) + .IsRequired(); + + builder.Property(x => x.IgnoreInCalculation) + .IsRequired(); + + builder.Property(x => x.CarId) + .IsRequired() + .HasConversion(); + + builder.HasOne(x => x.Car) + .WithMany(x => x.Consumptions); + } +} diff --git a/src/WebApi/Consumptions/ConsumptionId.cs b/src/WebApi/Consumptions/ConsumptionId.cs new file mode 100644 index 0000000..5494b4a --- /dev/null +++ b/src/WebApi/Consumptions/ConsumptionId.cs @@ -0,0 +1,7 @@ +using StronglyTypedIds; + +namespace Vegasco.WebApi.Consumptions; + + +[StronglyTypedId] +public partial struct ConsumptionId; diff --git a/src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.Designer.cs b/src/WebApi/Persistence/Migrations/20240817153531_Initial.Designer.cs similarity index 59% rename from src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.Designer.cs rename to src/WebApi/Persistence/Migrations/20240817153531_Initial.Designer.cs index e4149a5..88ce60d 100644 --- a/src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.Designer.cs +++ b/src/WebApi/Persistence/Migrations/20240817153531_Initial.Designer.cs @@ -12,15 +12,15 @@ using Vegasco.WebApi.Persistence; namespace Vegasco.WebApi.Persistence.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20240803173803_AddCarsAndUsers")] - partial class AddCarsAndUsers + [Migration("20240817153531_Initial")] + partial class Initial { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.7") + .HasAnnotation("ProductVersion", "8.0.8") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -28,7 +28,6 @@ namespace Vegasco.WebApi.Persistence.Migrations modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("uuid"); b.Property("Name") @@ -47,6 +46,33 @@ namespace Vegasco.WebApi.Persistence.Migrations b.ToTable("Cars"); }); + modelBuilder.Entity("Vegasco.WebApi.Consumptions.Consumption", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("double precision"); + + b.Property("CarId") + .HasColumnType("uuid"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Distance") + .HasColumnType("double precision"); + + b.Property("IgnoreInCalculation") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("CarId"); + + b.ToTable("Consumption"); + }); + modelBuilder.Entity("Vegasco.WebApi.Users.User", b => { b.Property("Id") @@ -68,6 +94,22 @@ namespace Vegasco.WebApi.Persistence.Migrations b.Navigation("User"); }); + modelBuilder.Entity("Vegasco.WebApi.Consumptions.Consumption", b => + { + b.HasOne("Vegasco.WebApi.Cars.Car", "Car") + .WithMany("Consumptions") + .HasForeignKey("CarId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Car"); + }); + + modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => + { + b.Navigation("Consumptions"); + }); + modelBuilder.Entity("Vegasco.WebApi.Users.User", b => { b.Navigation("Cars"); diff --git a/src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.cs b/src/WebApi/Persistence/Migrations/20240817153531_Initial.cs similarity index 57% rename from src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.cs rename to src/WebApi/Persistence/Migrations/20240817153531_Initial.cs index a3cceaf..57755c7 100644 --- a/src/WebApi/Persistence/Migrations/20240803173803_AddCarsAndUsers.cs +++ b/src/WebApi/Persistence/Migrations/20240817153531_Initial.cs @@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace Vegasco.WebApi.Persistence.Migrations { /// - public partial class AddCarsAndUsers : Migration + public partial class Initial : Migration { /// protected override void Up(MigrationBuilder migrationBuilder) @@ -41,15 +41,45 @@ namespace Vegasco.WebApi.Persistence.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "Consumption", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + DateTime = table.Column(type: "timestamp with time zone", nullable: false), + Distance = table.Column(type: "double precision", nullable: false), + Amount = table.Column(type: "double precision", nullable: false), + IgnoreInCalculation = table.Column(type: "boolean", nullable: false), + CarId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Consumption", x => x.Id); + table.ForeignKey( + name: "FK_Consumption_Cars_CarId", + column: x => x.CarId, + principalTable: "Cars", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + migrationBuilder.CreateIndex( name: "IX_Cars_UserId", table: "Cars", column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_Consumption_CarId", + table: "Consumption", + column: "CarId"); } /// protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropTable( + name: "Consumption"); + migrationBuilder.DropTable( name: "Cars"); diff --git a/src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index c29b621..9e44e83 100644 --- a/src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Vegasco.WebApi.Persistence.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.7") + .HasAnnotation("ProductVersion", "8.0.8") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -25,7 +25,6 @@ namespace Vegasco.WebApi.Persistence.Migrations modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => { b.Property("Id") - .ValueGeneratedOnAdd() .HasColumnType("uuid"); b.Property("Name") @@ -44,6 +43,33 @@ namespace Vegasco.WebApi.Persistence.Migrations b.ToTable("Cars"); }); + modelBuilder.Entity("Vegasco.WebApi.Consumptions.Consumption", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("double precision"); + + b.Property("CarId") + .HasColumnType("uuid"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Distance") + .HasColumnType("double precision"); + + b.Property("IgnoreInCalculation") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("CarId"); + + b.ToTable("Consumption"); + }); + modelBuilder.Entity("Vegasco.WebApi.Users.User", b => { b.Property("Id") @@ -65,6 +91,22 @@ namespace Vegasco.WebApi.Persistence.Migrations b.Navigation("User"); }); + modelBuilder.Entity("Vegasco.WebApi.Consumptions.Consumption", b => + { + b.HasOne("Vegasco.WebApi.Cars.Car", "Car") + .WithMany("Consumptions") + .HasForeignKey("CarId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Car"); + }); + + modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => + { + b.Navigation("Consumptions"); + }); + modelBuilder.Entity("Vegasco.WebApi.Users.User", b => { b.Navigation("Cars"); diff --git a/src/WebApi/WebApi.csproj b/src/WebApi/WebApi.csproj index 9790ef8..dcbadf5 100644 --- a/src/WebApi/WebApi.csproj +++ b/src/WebApi/WebApi.csproj @@ -14,10 +14,10 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -28,6 +28,8 @@ + + diff --git a/tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs b/tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs index 4376218..b9226af 100644 --- a/tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs +++ b/tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs @@ -38,7 +38,8 @@ public class CreateCarTests : IAsyncLifetime var createdCar = await response.Content.ReadFromJsonAsync(); createdCar.Should().BeEquivalentTo(createCarRequest, o => o.ExcludingMissingMembers()); - _dbContext.Cars.Should().ContainEquivalentOf(createdCar); + _dbContext.Cars.Should().ContainEquivalentOf(createdCar, o => o.Excluding(x => x!.Id)) + .Which.Id.Value.Should().Be(createdCar!.Id); } [Fact] diff --git a/tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs b/tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs index bc0ca8b..56a81e7 100644 --- a/tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs +++ b/tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs @@ -44,7 +44,10 @@ public class UpdateCarTests : IAsyncLifetime updatedCar!.Id.Should().Be(createdCar.Id); updatedCar.Should().BeEquivalentTo(updateCarRequest, o => o.ExcludingMissingMembers()); - _dbContext.Cars.Should().ContainEquivalentOf(updatedCar, o => o.ExcludingMissingMembers()); + _dbContext.Cars.Should().ContainEquivalentOf(updatedCar, o => + o.ExcludingMissingMembers() + .Excluding(x => x.Id)) + .Which.Id.Value.Should().Be(updatedCar.Id); } [Fact] @@ -67,7 +70,7 @@ public class UpdateCarTests : IAsyncLifetime validationProblemDetails!.Errors.Keys.Should().Contain(x => x.Equals(nameof(CreateCar.Request.Name), StringComparison.OrdinalIgnoreCase)); - _dbContext.Cars.Should().ContainSingle(x => x.Id == createdCar.Id) + _dbContext.Cars.Should().ContainSingle(x => x.Id.Value == createdCar.Id) .Which .Should().NotBeEquivalentTo(updateCarRequest, o => o.ExcludingMissingMembers()); } diff --git a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj index 9f191f4..355b9b1 100644 --- a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj +++ b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj @@ -11,14 +11,20 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - + + - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/tests/WebApi.Tests.System/WebApi.Tests.System.csproj b/tests/WebApi.Tests.System/WebApi.Tests.System.csproj index 2e0f217..4ab0a38 100644 --- a/tests/WebApi.Tests.System/WebApi.Tests.System.csproj +++ b/tests/WebApi.Tests.System/WebApi.Tests.System.csproj @@ -10,11 +10,17 @@ - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + -- 2.49.1 From 2463c11be3552addd32d90ea7b5eb25bf2be7bf1 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 23 Aug 2024 18:02:18 +0200 Subject: [PATCH 014/150] Add consumption logic and endpoints --- migrations/migration.sql | 22 +-- src/WebApi/Cars/GetCar.cs | 4 +- src/WebApi/Consumptions/CreateConsumption.cs | 74 ++++++++++ src/WebApi/Consumptions/DeleteConsumptions.cs | 30 +++++ src/WebApi/Consumptions/GetConsumption.cs | 32 +++++ src/WebApi/Consumptions/GetConsumptions.cs | 27 ++++ src/WebApi/Consumptions/UpdateConsumption.cs | 65 +++++++++ src/WebApi/Endpoints/EndpointExtensions.cs | 7 + .../Persistence/ApplicationDbContext.cs | 3 + ....cs => 20240818105918_Initial.Designer.cs} | 4 +- ...1_Initial.cs => 20240818105918_Initial.cs} | 12 +- .../ApplicationDbContextModelSnapshot.cs | 2 +- tests/WebApi.Tests.Integration/CarFaker.cs | 2 +- .../ConsumptionFaker.cs | 29 ++++ .../Consumptions/CreateConsumptionTests.cs | 95 +++++++++++++ .../Consumptions/DeleteConsumptionTests.cs | 82 ++++++++++++ .../Consumptions/GetConsumptionTests.cs | 88 ++++++++++++ .../Consumptions/GetConsumptionsTests.cs | 94 +++++++++++++ .../Consumptions/UpdateConsumptionTests.cs | 126 ++++++++++++++++++ .../FluentAssertionConfiguration.cs | 18 +++ .../CreateConsumptionRequestValidatorTests.cs | 100 ++++++++++++++ .../UpdateConsumptionRequestValidatorTests.cs | 86 ++++++++++++ 22 files changed, 979 insertions(+), 23 deletions(-) create mode 100644 src/WebApi/Consumptions/CreateConsumption.cs create mode 100644 src/WebApi/Consumptions/DeleteConsumptions.cs create mode 100644 src/WebApi/Consumptions/GetConsumption.cs create mode 100644 src/WebApi/Consumptions/GetConsumptions.cs create mode 100644 src/WebApi/Consumptions/UpdateConsumption.cs rename src/WebApi/Persistence/Migrations/{20240817153531_Initial.Designer.cs => 20240818105918_Initial.Designer.cs} (97%) rename src/WebApi/Persistence/Migrations/{20240817153531_Initial.cs => 20240818105918_Initial.cs} (91%) create mode 100644 tests/WebApi.Tests.Integration/ConsumptionFaker.cs create mode 100644 tests/WebApi.Tests.Integration/Consumptions/CreateConsumptionTests.cs create mode 100644 tests/WebApi.Tests.Integration/Consumptions/DeleteConsumptionTests.cs create mode 100644 tests/WebApi.Tests.Integration/Consumptions/GetConsumptionTests.cs create mode 100644 tests/WebApi.Tests.Integration/Consumptions/GetConsumptionsTests.cs create mode 100644 tests/WebApi.Tests.Integration/Consumptions/UpdateConsumptionTests.cs create mode 100644 tests/WebApi.Tests.Integration/FluentAssertionConfiguration.cs create mode 100644 tests/WebApi.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs create mode 100644 tests/WebApi.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs diff --git a/migrations/migration.sql b/migrations/migration.sql index 67a66e6..beeda51 100644 --- a/migrations/migration.sql +++ b/migrations/migration.sql @@ -9,7 +9,7 @@ START TRANSACTION; DO $EF$ BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN CREATE TABLE "Users" ( "Id" text NOT NULL, CONSTRAINT "PK_Users" PRIMARY KEY ("Id") @@ -19,7 +19,7 @@ END $EF$; DO $EF$ BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN CREATE TABLE "Cars" ( "Id" uuid NOT NULL, "Name" character varying(50) NOT NULL, @@ -32,39 +32,39 @@ END $EF$; DO $EF$ BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN - CREATE TABLE "Consumption" ( + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN + CREATE TABLE "Consumptions" ( "Id" uuid NOT NULL, "DateTime" timestamp with time zone NOT NULL, "Distance" double precision NOT NULL, "Amount" double precision NOT NULL, "IgnoreInCalculation" boolean NOT NULL, "CarId" uuid NOT NULL, - CONSTRAINT "PK_Consumption" PRIMARY KEY ("Id"), - CONSTRAINT "FK_Consumption_Cars_CarId" FOREIGN KEY ("CarId") REFERENCES "Cars" ("Id") ON DELETE CASCADE + CONSTRAINT "PK_Consumptions" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Consumptions_Cars_CarId" FOREIGN KEY ("CarId") REFERENCES "Cars" ("Id") ON DELETE CASCADE ); END IF; END $EF$; DO $EF$ BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN CREATE INDEX "IX_Cars_UserId" ON "Cars" ("UserId"); END IF; END $EF$; DO $EF$ BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN - CREATE INDEX "IX_Consumption_CarId" ON "Consumption" ("CarId"); + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN + CREATE INDEX "IX_Consumptions_CarId" ON "Consumptions" ("CarId"); END IF; END $EF$; DO $EF$ BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") - VALUES ('20240817153531_Initial', '8.0.8'); + VALUES ('20240818105918_Initial', '8.0.8'); END IF; END $EF$; COMMIT; diff --git a/src/WebApi/Cars/GetCar.cs b/src/WebApi/Cars/GetCar.cs index 14018a6..beb8432 100644 --- a/src/WebApi/Cars/GetCar.cs +++ b/src/WebApi/Cars/GetCar.cs @@ -13,12 +13,12 @@ public static class GetCar .WithTags("Cars"); } - public static async Task Endpoint( + private static async Task Endpoint( Guid id, ApplicationDbContext dbContext, CancellationToken cancellationToken) { - var car = await dbContext.Cars.FindAsync([new CarId(id)], cancellationToken: cancellationToken); + Car? car = await dbContext.Cars.FindAsync([new CarId(id)], cancellationToken: cancellationToken); if (car is null) { diff --git a/src/WebApi/Consumptions/CreateConsumption.cs b/src/WebApi/Consumptions/CreateConsumption.cs new file mode 100644 index 0000000..7d5def3 --- /dev/null +++ b/src/WebApi/Consumptions/CreateConsumption.cs @@ -0,0 +1,74 @@ +using FluentValidation; +using FluentValidation.Results; +using Vegasco.WebApi.Cars; +using Vegasco.WebApi.Common; +using Vegasco.WebApi.Persistence; + +namespace Vegasco.WebApi.Consumptions; + +public static class CreateConsumption +{ + public record Request(DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId); + + public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId); + + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapPost("consumptions", Endpoint) + .WithTags("Consumptions"); + } + + public class Validator : AbstractValidator + { + public Validator(TimeProvider timeProvider) + { + RuleFor(x => x.DateTime.ToUniversalTime()) + .LessThanOrEqualTo(timeProvider.GetUtcNow()) + .WithName(nameof(Request.DateTime)); + + RuleFor(x => x.Distance) + .GreaterThan(0); + + RuleFor(x => x.Amount) + .GreaterThan(0); + + RuleFor(x => x.CarId) + .NotEmpty(); + } + } + + private static async Task Endpoint( + ApplicationDbContext dbContext, + Request request, + IEnumerable> validators, + CancellationToken cancellationToken) + { + List failedValidations = await validators.ValidateAllAsync(request, cancellationToken); + if (failedValidations.Count > 0) + { + return TypedResults.BadRequest(new HttpValidationProblemDetails(failedValidations.ToCombinedDictionary())); + } + + Car? car = await dbContext.Cars.FindAsync([new CarId(request.CarId)], cancellationToken); + if (car is null) + { + return TypedResults.NotFound(); + } + + var consumption = new Consumption + { + DateTime = request.DateTime.ToUniversalTime(), + Distance = request.Distance, + Amount = request.Amount, + IgnoreInCalculation = request.IgnoreInCalculation, + CarId = new CarId(request.CarId) + }; + + dbContext.Consumptions.Add(consumption); + await dbContext.SaveChangesAsync(cancellationToken); + + return TypedResults.Created($"consumptions/{consumption.Id.Value}", + new Response(consumption.Id.Value, consumption.DateTime, consumption.Distance, consumption.Amount, consumption.IgnoreInCalculation, consumption.CarId.Value)); + } +} \ No newline at end of file diff --git a/src/WebApi/Consumptions/DeleteConsumptions.cs b/src/WebApi/Consumptions/DeleteConsumptions.cs new file mode 100644 index 0000000..aa93da5 --- /dev/null +++ b/src/WebApi/Consumptions/DeleteConsumptions.cs @@ -0,0 +1,30 @@ +using Vegasco.WebApi.Persistence; + +namespace Vegasco.WebApi.Consumptions; + +public static class DeleteConsumption +{ + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapDelete("consumptions/{id:guid}", Endpoint) + .WithTags("Consumptions"); + } + + private static async Task Endpoint( + ApplicationDbContext dbContext, + Guid id, + CancellationToken cancellationToken) + { + Consumption? consumption = await dbContext.Consumptions.FindAsync([new ConsumptionId(id)], cancellationToken); + if (consumption is null) + { + return TypedResults.NotFound(); + } + + dbContext.Consumptions.Remove(consumption); + await dbContext.SaveChangesAsync(cancellationToken); + + return TypedResults.NoContent(); + } +} \ No newline at end of file diff --git a/src/WebApi/Consumptions/GetConsumption.cs b/src/WebApi/Consumptions/GetConsumption.cs new file mode 100644 index 0000000..bcb9882 --- /dev/null +++ b/src/WebApi/Consumptions/GetConsumption.cs @@ -0,0 +1,32 @@ +using Vegasco.WebApi.Persistence; + +namespace Vegasco.WebApi.Consumptions; + +public static class GetConsumption +{ + public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId); + + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapGet("consumptions/{id:guid}", Endpoint) + .WithTags("Consumptions"); + } + + private static async Task Endpoint( + ApplicationDbContext dbContext, + Guid id, + CancellationToken cancellationToken) + { + Consumption? consumption = await dbContext.Consumptions.FindAsync([new ConsumptionId(id)], cancellationToken); + + if (consumption is null) + { + return TypedResults.NotFound(); + } + + var response = new Response(consumption.Id.Value, consumption.DateTime, consumption.Distance, + consumption.Amount, consumption.IgnoreInCalculation, consumption.CarId.Value); + return TypedResults.Ok(response); + } +} \ No newline at end of file diff --git a/src/WebApi/Consumptions/GetConsumptions.cs b/src/WebApi/Consumptions/GetConsumptions.cs new file mode 100644 index 0000000..bc13cee --- /dev/null +++ b/src/WebApi/Consumptions/GetConsumptions.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using Vegasco.WebApi.Persistence; + +namespace Vegasco.WebApi.Consumptions; + +public static class GetConsumptions +{ + public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId); + + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapGet("consumptions", Endpoint) + .WithTags("Consumptions"); + } + + private static async Task Endpoint( + ApplicationDbContext dbContext, + CancellationToken cancellationToken) + { + var consumptions = await dbContext.Consumptions + .Select(x => new Response(x.Id.Value, x.DateTime, x.Distance, x.Amount, x.IgnoreInCalculation, x.CarId.Value)) + .ToListAsync(cancellationToken); + + return TypedResults.Ok(consumptions); + } +} \ No newline at end of file diff --git a/src/WebApi/Consumptions/UpdateConsumption.cs b/src/WebApi/Consumptions/UpdateConsumption.cs new file mode 100644 index 0000000..8d27bad --- /dev/null +++ b/src/WebApi/Consumptions/UpdateConsumption.cs @@ -0,0 +1,65 @@ +using FluentValidation; +using FluentValidation.Results; +using Vegasco.WebApi.Common; +using Vegasco.WebApi.Persistence; + +namespace Vegasco.WebApi.Consumptions; + +public static class UpdateConsumption +{ + public record Request(DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation); + + public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId); + + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapPut("consumptions/{id:guid}", Endpoint) + .WithTags("Consumptions"); + } + + public class Validator : AbstractValidator + { + public Validator(TimeProvider timeProvider) + { + RuleFor(x => x.DateTime.ToUniversalTime()) + .LessThanOrEqualTo(timeProvider.GetUtcNow()) + .WithName(nameof(Request.DateTime)); + + RuleFor(x => x.Distance) + .GreaterThan(0); + + RuleFor(x => x.Amount) + .GreaterThan(0); + } + } + + private static async Task Endpoint( + ApplicationDbContext dbContext, + Guid id, + Request request, + IEnumerable> validators, + CancellationToken cancellationToken) + { + List failedValidations = await validators.ValidateAllAsync(request, cancellationToken); + if (failedValidations.Count > 0) + { + return TypedResults.BadRequest(new HttpValidationProblemDetails(failedValidations.ToCombinedDictionary())); + } + + Consumption? consumption = await dbContext.Consumptions.FindAsync([new ConsumptionId(id)], cancellationToken); + if (consumption is null) + { + return TypedResults.NotFound(); + } + + consumption.DateTime = request.DateTime.ToUniversalTime(); + consumption.Distance = request.Distance; + consumption.Amount = request.Amount; + consumption.IgnoreInCalculation = request.IgnoreInCalculation; + + await dbContext.SaveChangesAsync(cancellationToken); + + return TypedResults.Ok(new Response(consumption.Id.Value, consumption.DateTime, consumption.Distance, consumption.Amount, consumption.IgnoreInCalculation, consumption.CarId.Value)); + } +} \ No newline at end of file diff --git a/src/WebApi/Endpoints/EndpointExtensions.cs b/src/WebApi/Endpoints/EndpointExtensions.cs index 28f7df1..1481541 100644 --- a/src/WebApi/Endpoints/EndpointExtensions.cs +++ b/src/WebApi/Endpoints/EndpointExtensions.cs @@ -3,6 +3,7 @@ using Asp.Versioning.Conventions; using Microsoft.Extensions.DependencyInjection.Extensions; using Vegasco.WebApi.Cars; using Vegasco.WebApi.Common; +using Vegasco.WebApi.Consumptions; namespace Vegasco.WebApi.Endpoints; @@ -39,5 +40,11 @@ public static class EndpointExtensions CreateCar.MapEndpoint(versionedApis); UpdateCar.MapEndpoint(versionedApis); DeleteCar.MapEndpoint(versionedApis); + + GetConsumptions.MapEndpoint(versionedApis); + GetConsumption.MapEndpoint(versionedApis); + CreateConsumption.MapEndpoint(versionedApis); + UpdateConsumption.MapEndpoint(versionedApis); + DeleteConsumption.MapEndpoint(versionedApis); } } diff --git a/src/WebApi/Persistence/ApplicationDbContext.cs b/src/WebApi/Persistence/ApplicationDbContext.cs index e02de1d..6c436c9 100644 --- a/src/WebApi/Persistence/ApplicationDbContext.cs +++ b/src/WebApi/Persistence/ApplicationDbContext.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Vegasco.WebApi.Cars; using Vegasco.WebApi.Common; +using Vegasco.WebApi.Consumptions; using Vegasco.WebApi.Users; namespace Vegasco.WebApi.Persistence; @@ -11,6 +12,8 @@ public class ApplicationDbContext(DbContextOptions options public DbSet Users { get; set; } + public DbSet Consumptions { get; set; } + protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); diff --git a/src/WebApi/Persistence/Migrations/20240817153531_Initial.Designer.cs b/src/WebApi/Persistence/Migrations/20240818105918_Initial.Designer.cs similarity index 97% rename from src/WebApi/Persistence/Migrations/20240817153531_Initial.Designer.cs rename to src/WebApi/Persistence/Migrations/20240818105918_Initial.Designer.cs index 88ce60d..046955c 100644 --- a/src/WebApi/Persistence/Migrations/20240817153531_Initial.Designer.cs +++ b/src/WebApi/Persistence/Migrations/20240818105918_Initial.Designer.cs @@ -12,7 +12,7 @@ using Vegasco.WebApi.Persistence; namespace Vegasco.WebApi.Persistence.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20240817153531_Initial")] + [Migration("20240818105918_Initial")] partial class Initial { /// @@ -70,7 +70,7 @@ namespace Vegasco.WebApi.Persistence.Migrations b.HasIndex("CarId"); - b.ToTable("Consumption"); + b.ToTable("Consumptions"); }); modelBuilder.Entity("Vegasco.WebApi.Users.User", b => diff --git a/src/WebApi/Persistence/Migrations/20240817153531_Initial.cs b/src/WebApi/Persistence/Migrations/20240818105918_Initial.cs similarity index 91% rename from src/WebApi/Persistence/Migrations/20240817153531_Initial.cs rename to src/WebApi/Persistence/Migrations/20240818105918_Initial.cs index 57755c7..be2f349 100644 --- a/src/WebApi/Persistence/Migrations/20240817153531_Initial.cs +++ b/src/WebApi/Persistence/Migrations/20240818105918_Initial.cs @@ -42,7 +42,7 @@ namespace Vegasco.WebApi.Persistence.Migrations }); migrationBuilder.CreateTable( - name: "Consumption", + name: "Consumptions", columns: table => new { Id = table.Column(type: "uuid", nullable: false), @@ -54,9 +54,9 @@ namespace Vegasco.WebApi.Persistence.Migrations }, constraints: table => { - table.PrimaryKey("PK_Consumption", x => x.Id); + table.PrimaryKey("PK_Consumptions", x => x.Id); table.ForeignKey( - name: "FK_Consumption_Cars_CarId", + name: "FK_Consumptions_Cars_CarId", column: x => x.CarId, principalTable: "Cars", principalColumn: "Id", @@ -69,8 +69,8 @@ namespace Vegasco.WebApi.Persistence.Migrations column: "UserId"); migrationBuilder.CreateIndex( - name: "IX_Consumption_CarId", - table: "Consumption", + name: "IX_Consumptions_CarId", + table: "Consumptions", column: "CarId"); } @@ -78,7 +78,7 @@ namespace Vegasco.WebApi.Persistence.Migrations protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( - name: "Consumption"); + name: "Consumptions"); migrationBuilder.DropTable( name: "Cars"); diff --git a/src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index 9e44e83..390dc72 100644 --- a/src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -67,7 +67,7 @@ namespace Vegasco.WebApi.Persistence.Migrations b.HasIndex("CarId"); - b.ToTable("Consumption"); + b.ToTable("Consumptions"); }); modelBuilder.Entity("Vegasco.WebApi.Users.User", b => diff --git a/tests/WebApi.Tests.Integration/CarFaker.cs b/tests/WebApi.Tests.Integration/CarFaker.cs index 2d5bb5e..7364709 100644 --- a/tests/WebApi.Tests.Integration/CarFaker.cs +++ b/tests/WebApi.Tests.Integration/CarFaker.cs @@ -16,4 +16,4 @@ internal class CarFaker { return new UpdateCar.Request(_faker.Vehicle.Model()); } -} +} \ No newline at end of file diff --git a/tests/WebApi.Tests.Integration/ConsumptionFaker.cs b/tests/WebApi.Tests.Integration/ConsumptionFaker.cs new file mode 100644 index 0000000..d4e84a3 --- /dev/null +++ b/tests/WebApi.Tests.Integration/ConsumptionFaker.cs @@ -0,0 +1,29 @@ +using Bogus; +using Vegasco.WebApi.Consumptions; + +namespace WebApi.Tests.Integration; + +internal class ConsumptionFaker +{ + private readonly Faker _faker = new(); + + internal CreateConsumption.Request CreateConsumptionRequest(Guid carId) + { + return new CreateConsumption.Request( + _faker.Date.RecentOffset(), + _faker.Random.Int(1, 1_000), + _faker.Random.Int(20, 70), + _faker.Random.Bool(), + carId); + } + + internal UpdateConsumption.Request UpdateConsumptionRequest() + { + CreateConsumption.Request createRequest = CreateConsumptionRequest(default); + return new UpdateConsumption.Request( + createRequest.DateTime, + createRequest.Distance, + createRequest.Amount, + createRequest.IgnoreInCalculation); + } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.Integration/Consumptions/CreateConsumptionTests.cs b/tests/WebApi.Tests.Integration/Consumptions/CreateConsumptionTests.cs new file mode 100644 index 0000000..30887e9 --- /dev/null +++ b/tests/WebApi.Tests.Integration/Consumptions/CreateConsumptionTests.cs @@ -0,0 +1,95 @@ +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using System.Net; +using System.Net.Http.Json; +using Vegasco.WebApi.Cars; +using Vegasco.WebApi.Consumptions; +using Vegasco.WebApi.Persistence; + +namespace WebApi.Tests.Integration.Consumptions; + +[Collection(SharedTestCollection.Name)] +public class CreateConsumptionTests : IAsyncLifetime +{ + private readonly WebAppFactory _factory; + private readonly IServiceScope _scope; + private readonly ApplicationDbContext _dbContext; + + private readonly CarFaker _carFaker = new(); + private readonly ConsumptionFaker _consumptionFaker = new(); + + public CreateConsumptionTests(WebAppFactory factory) + { + _factory = factory; + _scope = _factory.Services.CreateScope(); + _dbContext = _scope.ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task CreateConsumption_ShouldCreateConsumption_WhenRequestIsValid() + { + // Arrange + CreateCar.Response createdCarResponse = await CreateCarAsync(); + + CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id); + + // Act + using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.Created); + var createdConsumption = await response.Content.ReadFromJsonAsync(); + createdConsumption.Should().BeEquivalentTo(createConsumptionRequest, o => o.ExcludingMissingMembers()); + + _dbContext.Consumptions.Should().HaveCount(1) + .And.ContainEquivalentOf(createdConsumption, o => + o.ExcludingMissingMembers() + .Excluding(x => x!.Id) + .Excluding(x => x!.CarId)); + + Consumption singleConsumption = _dbContext.Consumptions.Single(); + singleConsumption.Id.Value.Should().Be(createdConsumption!.Id); + singleConsumption.CarId.Value.Should().Be(createdConsumption.CarId); + } + + [Fact] + public async Task CreateConsumption_ShouldReturnValidationProblems_WhenRequestIsInvalid() + { + // Arrange + CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(Guid.Empty); + + // Act + using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var validationProblemDetails = await response.Content.ReadFromJsonAsync(); + validationProblemDetails!.Errors.Keys.Should().Contain(x => + x.Equals(nameof(createConsumptionRequest.CarId), StringComparison.OrdinalIgnoreCase)); + + _dbContext.Consumptions.Should().NotContainEquivalentOf(createConsumptionRequest, o => o.ExcludingMissingMembers()); + } + + private async Task CreateCarAsync() + { + CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest(); + using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + createCarResponse.EnsureSuccessStatusCode(); + var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); + return createdCarResponse!; + } + + public Task InitializeAsync() + { + FluentAssertionConfiguration.SetupGlobalConfig(); + return Task.CompletedTask; + } + + public async Task DisposeAsync() + { + _scope.Dispose(); + await _dbContext.DisposeAsync(); + await _factory.ResetDatabaseAsync(); + } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.Integration/Consumptions/DeleteConsumptionTests.cs b/tests/WebApi.Tests.Integration/Consumptions/DeleteConsumptionTests.cs new file mode 100644 index 0000000..af72f99 --- /dev/null +++ b/tests/WebApi.Tests.Integration/Consumptions/DeleteConsumptionTests.cs @@ -0,0 +1,82 @@ +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using System.Net; +using System.Net.Http.Json; +using Vegasco.WebApi.Cars; +using Vegasco.WebApi.Consumptions; +using Vegasco.WebApi.Persistence; + +namespace WebApi.Tests.Integration.Consumptions; + +[Collection(SharedTestCollection.Name)] +public class DeleteConsumptionTests : IAsyncLifetime +{ + private readonly WebAppFactory _factory; + private readonly IServiceScope _scope; + private readonly ApplicationDbContext _dbContext; + + private readonly CarFaker _carFaker = new(); + private readonly ConsumptionFaker _consumptionFaker = new(); + + public DeleteConsumptionTests(WebAppFactory factory) + { + _factory = factory; + _scope = _factory.Services.CreateScope(); + _dbContext = _scope.ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task DeleteConsumption_ShouldDeleteConsumption_WhenConsumptionExists() + { + // Arrange + CreateConsumption.Response createdConsumption = await CreateConsumptionAsync(); + + // Act + using HttpResponseMessage response = await _factory.HttpClient.DeleteAsync($"v1/consumptions/{createdConsumption.Id}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NoContent); + _dbContext.Consumptions.Should().NotContain(x => x.Id.Value == createdConsumption.Id); + } + + [Fact] + public async Task DeleteConsumption_ShouldReturnNotFound_WhenConsumptionDoesNotExist() + { + // Arrange + var consumptionId = Guid.NewGuid(); + + // Act + using HttpResponseMessage response = await _factory.HttpClient.DeleteAsync($"v1/consumptions/{consumptionId}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + private async Task CreateConsumptionAsync() + { + CreateCar.Response createdCarResponse = await CreateCarAsync(); + CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id); + using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest); + response.EnsureSuccessStatusCode(); + var createdConsumption = await response.Content.ReadFromJsonAsync(); + return createdConsumption!; + } + + private async Task CreateCarAsync() + { + CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest(); + using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + createCarResponse.EnsureSuccessStatusCode(); + var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); + return createdCarResponse!; + } + + public Task InitializeAsync() => Task.CompletedTask; + + public async Task DisposeAsync() + { + _scope.Dispose(); + await _dbContext.DisposeAsync(); + await _factory.ResetDatabaseAsync(); + } +} diff --git a/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionTests.cs b/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionTests.cs new file mode 100644 index 0000000..2e490f9 --- /dev/null +++ b/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionTests.cs @@ -0,0 +1,88 @@ +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using System.Net; +using System.Net.Http.Json; +using Vegasco.WebApi.Cars; +using Vegasco.WebApi.Consumptions; +using Vegasco.WebApi.Persistence; + +namespace WebApi.Tests.Integration.Consumptions; + +[Collection(SharedTestCollection.Name)] +public class GetConsumptionTests : IAsyncLifetime +{ + private readonly WebAppFactory _factory; + private readonly IServiceScope _scope; + private readonly ApplicationDbContext _dbContext; + + private readonly CarFaker _carFaker = new(); + private readonly ConsumptionFaker _consumptionFaker = new(); + + public GetConsumptionTests(WebAppFactory factory) + { + _factory = factory; + _scope = _factory.Services.CreateScope(); + _dbContext = _scope.ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task GetConsumption_ShouldReturnConsumption_WhenConsumptionExist() + { + // Arrange + CreateConsumption.Response createdConsumption = await CreateConsumptionAsync(); + + // Act + using HttpResponseMessage response = await _factory.HttpClient.GetAsync($"v1/consumptions/{createdConsumption.Id}"); + + // Assert + var content = await response.Content.ReadAsStringAsync(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + var consumption = await response.Content.ReadFromJsonAsync(); + consumption.Should().BeEquivalentTo(createdConsumption); + } + + [Fact] + public async Task GetConsumptions_ShouldReturnNotFound_WhenConsumptionDoesNotExist() + { + // Arrange + var consumptionId = Guid.NewGuid(); + + // Act + using HttpResponseMessage response = await _factory.HttpClient.GetAsync($"v1/consumptions{consumptionId}"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + private async Task CreateConsumptionAsync() + { + CreateCar.Response createdCarResponse = await CreateCarAsync(); + CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id); + using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest); + response.EnsureSuccessStatusCode(); + var createdConsumption = await response.Content.ReadFromJsonAsync(); + return createdConsumption!; + } + + private async Task CreateCarAsync() + { + CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest(); + using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + createCarResponse.EnsureSuccessStatusCode(); + var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); + return createdCarResponse!; + } + + public Task InitializeAsync() + { + FluentAssertionConfiguration.SetupGlobalConfig(); + return Task.CompletedTask; + } + + public async Task DisposeAsync() + { + _scope.Dispose(); + await _dbContext.DisposeAsync(); + await _factory.ResetDatabaseAsync(); + } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionsTests.cs b/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionsTests.cs new file mode 100644 index 0000000..553879b --- /dev/null +++ b/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionsTests.cs @@ -0,0 +1,94 @@ +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using System.Net; +using System.Net.Http.Json; +using Vegasco.WebApi.Cars; +using Vegasco.WebApi.Consumptions; +using Vegasco.WebApi.Persistence; + +namespace WebApi.Tests.Integration.Consumptions; + +[Collection(SharedTestCollection.Name)] +public class GetConsumptionsTests : IAsyncLifetime +{ + private readonly WebAppFactory _factory; + private readonly IServiceScope _scope; + private readonly ApplicationDbContext _dbContext; + + private readonly CarFaker _carFaker = new(); + private readonly ConsumptionFaker _consumptionFaker = new(); + + public GetConsumptionsTests(WebAppFactory factory) + { + _factory = factory; + _scope = _factory.Services.CreateScope(); + _dbContext = _scope.ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task GetConsumptions_ShouldReturnConsumptions_WhenConsumptionsExist() + { + // Arrange + List createdConsumptions = []; + const int numberOfConsumptions = 3; + for (var i = 0; i < numberOfConsumptions; i++) + { + CreateConsumption.Response createdConsumption = await CreateConsumptionAsync(); + createdConsumptions.Add(createdConsumption); + } + + // Act + using HttpResponseMessage response = await _factory.HttpClient.GetAsync("v1/consumptions"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var consumptions = await response.Content.ReadFromJsonAsync>(); + consumptions.Should().BeEquivalentTo(createdConsumptions); + } + + [Fact] + public async Task GetConsumptions_ShouldReturnEmptyList_WhenNoConsumptionsExist() + { + // Arrange + + // Act + using HttpResponseMessage response = await _factory.HttpClient.GetAsync("v1/consumptions"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var consumptions = await response.Content.ReadFromJsonAsync>(); + consumptions.Should().BeEmpty(); + } + + private async Task CreateConsumptionAsync() + { + CreateCar.Response createdCarResponse = await CreateCarAsync(); + CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id); + using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest); + response.EnsureSuccessStatusCode(); + var createdConsumption = await response.Content.ReadFromJsonAsync(); + return createdConsumption!; + } + + private async Task CreateCarAsync() + { + CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest(); + using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + createCarResponse.EnsureSuccessStatusCode(); + var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); + return createdCarResponse!; + } + + public Task InitializeAsync() + { + FluentAssertionConfiguration.SetupGlobalConfig(); + return Task.CompletedTask; + } + + public async Task DisposeAsync() + { + _scope.Dispose(); + await _dbContext.DisposeAsync(); + await _factory.ResetDatabaseAsync(); + } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.Integration/Consumptions/UpdateConsumptionTests.cs b/tests/WebApi.Tests.Integration/Consumptions/UpdateConsumptionTests.cs new file mode 100644 index 0000000..723f7eb --- /dev/null +++ b/tests/WebApi.Tests.Integration/Consumptions/UpdateConsumptionTests.cs @@ -0,0 +1,126 @@ +using FluentAssertions; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using System.Net; +using System.Net.Http.Json; +using Vegasco.WebApi.Cars; +using Vegasco.WebApi.Consumptions; +using Vegasco.WebApi.Persistence; + +namespace WebApi.Tests.Integration.Consumptions; + +[Collection(SharedTestCollection.Name)] +public class UpdateConsumptionTests : IAsyncLifetime +{ + private readonly WebAppFactory _factory; + private readonly IServiceScope _scope; + private readonly ApplicationDbContext _dbContext; + + private readonly CarFaker _carFaker = new(); + private readonly ConsumptionFaker _consumptionFaker = new(); + + public UpdateConsumptionTests(WebAppFactory factory) + { + _factory = factory; + _scope = _factory.Services.CreateScope(); + _dbContext = _scope.ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task UpdateConsumption_ShouldCreateConsumption_WhenRequestIsValid() + { + // Arrange + CreateConsumption.Response createdConsumption = await CreateConsumptionAsync(); + UpdateConsumption.Request updateConsumptionRequest = _consumptionFaker.UpdateConsumptionRequest(); + + // Act + using HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/consumptions/{createdConsumption.Id}", updateConsumptionRequest); + + // Assert + var content = await response.Content.ReadAsStringAsync(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + var updatedConsumption = await response.Content.ReadFromJsonAsync(); + updatedConsumption.Should().BeEquivalentTo(updateConsumptionRequest, o => o.ExcludingMissingMembers()); + + _dbContext.Consumptions.Should().HaveCount(1) + .And.ContainEquivalentOf(updatedConsumption, o => + o.ExcludingMissingMembers() + .Excluding(x => x!.Id) + .Excluding(x => x!.CarId)); + + Consumption singleConsumption = _dbContext.Consumptions.Single(); + singleConsumption.Id.Value.Should().Be(updatedConsumption!.Id); + singleConsumption.CarId.Value.Should().Be(updatedConsumption.CarId); + } + + [Fact] + public async Task UpdateConsumption_ShouldReturnValidationProblems_WhenRequestIsInvalid() + { + // Arrange + CreateConsumption.Response createdConsumption = await CreateConsumptionAsync(); + UpdateConsumption.Request updateConsumptionRequest = _consumptionFaker.UpdateConsumptionRequest() with { Distance = -42 }; + var randomGuid = Guid.NewGuid(); + + // Act + using HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/consumptions/{randomGuid}", updateConsumptionRequest); + + // Assert + var content = await response.Content.ReadAsStringAsync(); + response.StatusCode.Should().Be(HttpStatusCode.BadRequest); + var validationProblemDetails = await response.Content.ReadFromJsonAsync(); + validationProblemDetails!.Errors.Keys.Should().Contain(x => + x.Equals(nameof(updateConsumptionRequest.Distance), StringComparison.OrdinalIgnoreCase)); + + _dbContext.Consumptions.Should().NotContainEquivalentOf(updateConsumptionRequest); + } + + [Fact] + public async Task UpdateConsumption_ShouldReturnNotFound_WhenConsumptionDoesNotExist() + { + // Arrange + CreateConsumption.Response createdConsumption = await CreateConsumptionAsync(); + UpdateConsumption.Request updateConsumptionRequest = _consumptionFaker.UpdateConsumptionRequest(); + var randomGuid = Guid.NewGuid(); + + // Act + using HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/consumptions/{randomGuid}", updateConsumptionRequest); + + // Assert + var content = await response.Content.ReadAsStringAsync(); + response.StatusCode.Should().Be(HttpStatusCode.NotFound); + + _dbContext.Consumptions.Should().NotContainEquivalentOf(updateConsumptionRequest); + } + + private async Task CreateConsumptionAsync() + { + CreateCar.Response createdCarResponse = await CreateCarAsync(); + CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id); + using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest); + response.EnsureSuccessStatusCode(); + var createdConsumption = await response.Content.ReadFromJsonAsync(); + return createdConsumption!; + } + + private async Task CreateCarAsync() + { + CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest(); + using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + createCarResponse.EnsureSuccessStatusCode(); + var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); + return createdCarResponse!; + } + + public Task InitializeAsync() + { + FluentAssertionConfiguration.SetupGlobalConfig(); + return Task.CompletedTask; + } + + public async Task DisposeAsync() + { + _scope.Dispose(); + await _dbContext.DisposeAsync(); + await _factory.ResetDatabaseAsync(); + } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.Integration/FluentAssertionConfiguration.cs b/tests/WebApi.Tests.Integration/FluentAssertionConfiguration.cs new file mode 100644 index 0000000..019e491 --- /dev/null +++ b/tests/WebApi.Tests.Integration/FluentAssertionConfiguration.cs @@ -0,0 +1,18 @@ +using FluentAssertions; + +namespace WebApi.Tests.Integration; +internal static class FluentAssertionConfiguration +{ + private const int DateTimeComparisonPrecision = 100; + + internal static void SetupGlobalConfig() + { + AssertionOptions.AssertEquivalencyUsing(options => options + .Using(ctx => ctx.Subject.ToUniversalTime().Should().BeCloseTo(ctx.Expectation.ToUniversalTime(), TimeSpan.FromMilliseconds(DateTimeComparisonPrecision))) + .WhenTypeIs()); + + AssertionOptions.AssertEquivalencyUsing(options => options + .Using(ctx => ctx.Subject.ToUniversalTime().Should().BeCloseTo(ctx.Expectation.ToUniversalTime(), TimeSpan.FromMilliseconds(DateTimeComparisonPrecision))) + .WhenTypeIs()); + } +} diff --git a/tests/WebApi.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs b/tests/WebApi.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs new file mode 100644 index 0000000..fa7f346 --- /dev/null +++ b/tests/WebApi.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs @@ -0,0 +1,100 @@ +using FluentAssertions; +using FluentValidation.Results; +using NSubstitute; +using Vegasco.WebApi.Consumptions; + +namespace WebApi.Tests.Unit.Consumptions; +public class CreateConsumptionRequestValidatorTests +{ + private readonly CreateConsumption.Validator _sut; + private readonly TimeProvider _timeProvider = Substitute.For(); + + private readonly DateTimeOffset _utcNow = new DateTimeOffset(2024, 8, 18, 13, 2, 53, TimeSpan.Zero); + + private readonly CreateConsumption.Request _validRequest; + + public CreateConsumptionRequestValidatorTests() + { + _timeProvider.GetUtcNow().Returns(_utcNow); + _sut = new CreateConsumption.Validator(_timeProvider); + + _validRequest = new CreateConsumption.Request( + _utcNow.AddDays(-1), + 1, + 1, + false, + Guid.NewGuid()); + } + + [Fact] + public async Task ValidateAsync_ShouldBeValid_WhenRequestIsValid() + { + // Arrange + + // Act + ValidationResult? result = await _sut.ValidateAsync(_validRequest); + + // Assert + result.IsValid.Should().BeTrue(); + } + + [Fact] + public async Task ValidateAsync_ShouldBeInvalid_WhenDateTimeIsGreaterThanUtcNow() + { + // Arrange + CreateConsumption.Request request = _validRequest with { DateTime = _utcNow.AddDays(1) }; + + // Act + ValidationResult? result = await _sut.ValidateAsync(request); + + // Assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(CreateConsumption.Request.DateTime)); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + public async Task ValidateAsync_ShouldBeInvalid_WhenDistanceIsLessThanOrEqualToZero(double distance) + { + // Arrange + CreateConsumption.Request request = _validRequest with { Distance = distance }; + + // Act + ValidationResult? result = await _sut.ValidateAsync(request); + + // Assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(CreateConsumption.Request.Distance)); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + public async Task ValidateAsync_ShouldBeInvalid_WhenAmountIsLessThanOrEqualToZero(double amount) + { + // Arrange + CreateConsumption.Request request = _validRequest with { Amount = amount }; + + // Act + ValidationResult? result = await _sut.ValidateAsync(request); + + // Assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(CreateConsumption.Request.Amount)); + } + + [Fact] + public async Task ValidateAsync_ShouldBeInvalid_WhenCarIdIsEmpty() + { + // Arrange + CreateConsumption.Request request = _validRequest with { CarId = Guid.Empty }; + + // Act + ValidationResult? result = await _sut.ValidateAsync(request); + + // Assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(CreateConsumption.Request.CarId)); + } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs b/tests/WebApi.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs new file mode 100644 index 0000000..14a91e8 --- /dev/null +++ b/tests/WebApi.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs @@ -0,0 +1,86 @@ +using FluentAssertions; +using FluentValidation.Results; +using NSubstitute; +using Vegasco.WebApi.Consumptions; + +namespace WebApi.Tests.Unit.Consumptions; + +public class UpdateConsumptionRequestValidatorTests +{ + private readonly UpdateConsumption.Validator _sut; + private readonly TimeProvider _timeProvider = Substitute.For(); + + private readonly DateTimeOffset _utcNow = new DateTimeOffset(2024, 8, 18, 13, 2, 53, TimeSpan.Zero); + + private readonly UpdateConsumption.Request _validRequest; + + public UpdateConsumptionRequestValidatorTests() + { + _timeProvider.GetUtcNow().Returns(_utcNow); + _sut = new UpdateConsumption.Validator(_timeProvider); + + _validRequest = new UpdateConsumption.Request( + _utcNow.AddDays(-1), + 1, + 1, + false); + } + + [Fact] + public async Task ValidateAsync_ShouldBeValid_WhenRequestIsValid() + { + // Arrange + + // Act + ValidationResult? result = await _sut.ValidateAsync(_validRequest); + + // Assert + result.IsValid.Should().BeTrue(); + } + + [Fact] + public async Task ValidateAsync_ShouldBeInvalid_WhenDateTimeIsGreaterThanUtcNow() + { + // Arrange + UpdateConsumption.Request request = _validRequest with { DateTime = _utcNow.AddDays(1) }; + + // Act + ValidationResult? result = await _sut.ValidateAsync(request); + + // Assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(UpdateConsumption.Request.DateTime)); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + public async Task ValidateAsync_ShouldBeInvalid_WhenDistanceIsLessThanOrEqualToZero(double distance) + { + // Arrange + UpdateConsumption.Request request = _validRequest with { Distance = distance }; + + // Act + ValidationResult? result = await _sut.ValidateAsync(request); + + // Assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(UpdateConsumption.Request.Distance)); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + public async Task ValidateAsync_ShouldBeInvalid_WhenAmountIsLessThanOrEqualToZero(double amount) + { + // Arrange + UpdateConsumption.Request request = _validRequest with { Amount = amount }; + + // Act + ValidationResult? result = await _sut.ValidateAsync(request); + + // Assert + result.IsValid.Should().BeFalse(); + result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(UpdateConsumption.Request.Amount)); + } +} \ No newline at end of file -- 2.49.1 From e20f713fdb060965c1b55221091e17d0eaa2cb27 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 23 Aug 2024 18:05:34 +0200 Subject: [PATCH 015/150] Add initial cicd yaml --- .drone.yml | 66 ++++++++++++++++++++++++++++++++++++++++++++++ vegasco-server.sln | 1 + 2 files changed, 67 insertions(+) create mode 100644 .drone.yml diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..e84218b --- /dev/null +++ b/.drone.yml @@ -0,0 +1,66 @@ +kind: pipeline +type: docker +name: Pipeline + +trigger: + event: + include: + - push + - pull_request + - custom + +steps: + + - name: compile + image: mcr.microsoft.com/dotnet/sdk:8.0-alpine + environment: + CI_WORKSPACE: "/drone/src" + commands: + - dotnet build + volumes: + - name: dockersock + path: /var/run + + - name: test + image: quay.io/testcontainers/dind-drone-plugin + environment: + CI_WORKSPACE: "/drone/src" + settings: + build_image: mcr.microsoft.com/dotnet/sdk:8.0-alpine + cmd: + - dotnet test --no-build + volumes: + - name: dockersock + path: /var/run + depends_on: + - compile + + - name: Telegram notification + image: appleboy/drone-telegram + settings: + token: + from_secret: telegram_token + to: + from_secret: telegram_user_id + when: + status: + - success + - failure + depends_on: + - compile + - test + - docker build and push + - deploy to test + - deploy to prod + +services: + - name: docker + image: docker:dind + privileged: true + volumes: + - name: dockersock + path: /var/run + +volumes: + - name: dockersock + temp: { } diff --git a/vegasco-server.sln b/vegasco-server.sln index 3f6457e..68690a8 100644 --- a/vegasco-server.sln +++ b/vegasco-server.sln @@ -10,6 +10,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A16251C2-47DB-4017-812B-CA18B280E049}" ProjectSection(SolutionItems) = preProject README.md = README.md + .drone.yml = .drone.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{437DE053-1DAB-4EEF-BEA6-E3B5179692F8}" -- 2.49.1 From 70f47b0dd136d85d5cd8a561a50f5665bc9ab613 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 23 Aug 2024 18:15:28 +0200 Subject: [PATCH 016/150] Replace with gitea actions --- .drone.yml | 66 ------------------------------------- .gitea/workflows/build.yaml | 62 ++++++++++++++++++++++++++++++++++ vegasco-server.sln | 7 +++- 3 files changed, 68 insertions(+), 67 deletions(-) delete mode 100644 .drone.yml create mode 100644 .gitea/workflows/build.yaml diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index e84218b..0000000 --- a/.drone.yml +++ /dev/null @@ -1,66 +0,0 @@ -kind: pipeline -type: docker -name: Pipeline - -trigger: - event: - include: - - push - - pull_request - - custom - -steps: - - - name: compile - image: mcr.microsoft.com/dotnet/sdk:8.0-alpine - environment: - CI_WORKSPACE: "/drone/src" - commands: - - dotnet build - volumes: - - name: dockersock - path: /var/run - - - name: test - image: quay.io/testcontainers/dind-drone-plugin - environment: - CI_WORKSPACE: "/drone/src" - settings: - build_image: mcr.microsoft.com/dotnet/sdk:8.0-alpine - cmd: - - dotnet test --no-build - volumes: - - name: dockersock - path: /var/run - depends_on: - - compile - - - name: Telegram notification - image: appleboy/drone-telegram - settings: - token: - from_secret: telegram_token - to: - from_secret: telegram_user_id - when: - status: - - success - - failure - depends_on: - - compile - - test - - docker build and push - - deploy to test - - deploy to prod - -services: - - name: docker - image: docker:dind - privileged: true - volumes: - - name: dockersock - path: /var/run - -volumes: - - name: dockersock - temp: { } diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..206379f --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,62 @@ +name: Build Vegasco Server +on: + workflow_run: + types: + - requested + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Install docker + uses: papodaca/install-docker-action@main + + - name: Install dependencies + run: apt update && apt install -y curl + + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build + run: dotnet build + + - name: Test + run: dotnet test + + ### Report via telegram + + - name: Get 8 characters of commit id + uses: bhowell2/github-substring-action@1.0.2 + id: commit-hash-short + if: ${{ always() }} + with: + value: ${{ gitea.event.commits[0].id }} + length_from_start: 8 + + - name: Clean commit message + uses: mad9000/actions-find-and-replace-string@3 + id: commit-message-clean + with: + source: ${{ gitea.event.commits[0].message }} + find: '\n' + replace: '' + + - name: Telegram notification + uses: appleboy/telegram-action@master + if: ${{ always() }} + env: + STATUS_ICON: ${{ job.status == 'success' && '✅' || job.status == 'failure' && '❌' || '❕' }} + with: + to: ${{ secrets.TELEGRAM_TO }} + token: ${{ secrets.TELEGRAM_TOKEN }} + format: markdown + message: | + ${{ env.STATUS_ICON }} Build #${{ gitea.run_number }} of `${{ gitea.repository }}`: ${{ job.status }} + + 📝 Commit by ${{ gitea.actor }} on `${{ gitea.ref_name }}`: + [${{ steps.commit-message-clean.outputs.value }} - ${{ steps.commit-hash-short.outputs.substring }}](${{ gitea.event.commits[0].url }}) + + 🌐 ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_number }} diff --git a/vegasco-server.sln b/vegasco-server.sln index 68690a8..f91c064 100644 --- a/vegasco-server.sln +++ b/vegasco-server.sln @@ -10,7 +10,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A16251C2-47DB-4017-812B-CA18B280E049}" ProjectSection(SolutionItems) = preProject README.md = README.md - .drone.yml = .drone.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{437DE053-1DAB-4EEF-BEA6-E3B5179692F8}" @@ -21,6 +20,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests.Integration", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.System", "tests\WebApi.Tests.System\WebApi.Tests.System.csproj", "{21418359-4A20-4F4A-B26C-A75A2B70AA10}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gitea_workflows", "gitea_workflows", "{B149FCA4-4FA0-4987-A451-75782BB0BD02}" + ProjectSection(SolutionItems) = preProject + .gitea\workflows\build.yaml = .gitea\workflows\build.yaml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -52,6 +56,7 @@ Global {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} {21418359-4A20-4F4A-B26C-A75A2B70AA10} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} + {B149FCA4-4FA0-4987-A451-75782BB0BD02} = {A16251C2-47DB-4017-812B-CA18B280E049} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7813E32D-AE19-479C-853B-063882D2D05A} -- 2.49.1 From f4846bc66a6f7e0e3df300c3e6da3daebc19714f Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 23 Aug 2024 18:17:27 +0200 Subject: [PATCH 017/150] Update on --- .gitea/workflows/build.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 206379f..948f282 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -1,5 +1,7 @@ name: Build Vegasco Server on: + push: + pull_request: workflow_run: types: - requested -- 2.49.1 From 4a46c46222121cc021c1ac27ce58495bd44d00fe Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 23 Aug 2024 18:20:35 +0200 Subject: [PATCH 018/150] Fix setting up pipeline --- .gitea/workflows/build.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 948f282..923e13f 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -13,15 +13,13 @@ jobs: - name: Install docker uses: papodaca/install-docker-action@main - - name: Install dependencies - run: apt update && apt install -y curl + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '8.x' - name: Check out repository code uses: actions/checkout@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Build run: dotnet build -- 2.49.1 From 155ed22fb0d01956e3b1fd1991045074cca7fc52 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 23 Aug 2024 18:31:54 +0200 Subject: [PATCH 019/150] Explicitly specify package --- tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj | 1 + tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj index 355b9b1..c7764a0 100644 --- a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj +++ b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj @@ -17,6 +17,7 @@ + diff --git a/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj b/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj index 2f6089a..d72ac6c 100644 --- a/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj +++ b/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj @@ -15,6 +15,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + -- 2.49.1 From 1c88d2b2c6af505e300ff1f93e50976835eff044 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 23 Aug 2024 18:32:01 +0200 Subject: [PATCH 020/150] Exclude empty test project --- .gitea/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 923e13f..77e97e4 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -24,7 +24,7 @@ jobs: run: dotnet build - name: Test - run: dotnet test + run: dotnet test --filter FullyQualifiedName!~System ### Report via telegram -- 2.49.1 From d19d68f5a24190a54c7fad6fa1fa4b2a076f982c Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 23 Aug 2024 18:35:51 +0200 Subject: [PATCH 021/150] Setup docker in pipeline --- .gitea/workflows/build.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 77e97e4..d2ecb25 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -13,6 +13,9 @@ jobs: - name: Install docker uses: papodaca/install-docker-action@main + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - uses: actions/setup-dotnet@v4 with: dotnet-version: '8.x' -- 2.49.1 From dcb82414b9079cc9ea3b50ef700f565876197a9b Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 23 Aug 2024 18:55:05 +0200 Subject: [PATCH 022/150] Add nbgv and add server info endpoint --- Directory.Build.props | 9 +++++ src/WebApi/Endpoints/EndpointExtensions.cs | 3 ++ src/WebApi/Info/GetServerInfo.cs | 29 +++++++++++++++ .../Info/GetServerInfoTests.cs | 35 +++++++++++++++++++ vegasco-server.sln | 1 + version.json | 7 ++++ 6 files changed, 84 insertions(+) create mode 100644 Directory.Build.props create mode 100644 src/WebApi/Info/GetServerInfo.cs create mode 100644 tests/WebApi.Tests.Integration/Info/GetServerInfoTests.cs create mode 100644 version.json diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..5023fe7 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,9 @@ + + + + + all + 3.6.141 + + + \ No newline at end of file diff --git a/src/WebApi/Endpoints/EndpointExtensions.cs b/src/WebApi/Endpoints/EndpointExtensions.cs index 1481541..5cc9c9c 100644 --- a/src/WebApi/Endpoints/EndpointExtensions.cs +++ b/src/WebApi/Endpoints/EndpointExtensions.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Vegasco.WebApi.Cars; using Vegasco.WebApi.Common; using Vegasco.WebApi.Consumptions; +using Vegasco.WebApi.Info; namespace Vegasco.WebApi.Endpoints; @@ -46,5 +47,7 @@ public static class EndpointExtensions CreateConsumption.MapEndpoint(versionedApis); UpdateConsumption.MapEndpoint(versionedApis); DeleteConsumption.MapEndpoint(versionedApis); + + GetServerInfo.MapEndpoint(versionedApis); } } diff --git a/src/WebApi/Info/GetServerInfo.cs b/src/WebApi/Info/GetServerInfo.cs new file mode 100644 index 0000000..f63cd73 --- /dev/null +++ b/src/WebApi/Info/GetServerInfo.cs @@ -0,0 +1,29 @@ +using Microsoft.AspNetCore.Http.HttpResults; + +namespace Vegasco.WebApi.Info; + +public class GetServerInfo +{ + public record Response( + string FullVersion, + string CommitId, + DateTime CommitDate, + string Environment); + + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapGet("info/server", Endpoint) + .WithTags("Info"); + } + + private static Ok Endpoint( + IHostEnvironment environment) + { + return TypedResults.Ok(new Response( + ThisAssembly.AssemblyInformationalVersion, + ThisAssembly.GitCommitId, + ThisAssembly.GitCommitDate, + environment.EnvironmentName)); + } +} \ No newline at end of file diff --git a/tests/WebApi.Tests.Integration/Info/GetServerInfoTests.cs b/tests/WebApi.Tests.Integration/Info/GetServerInfoTests.cs new file mode 100644 index 0000000..1ce6a14 --- /dev/null +++ b/tests/WebApi.Tests.Integration/Info/GetServerInfoTests.cs @@ -0,0 +1,35 @@ +using System.Net.Http.Json; +using FluentAssertions; +using FluentAssertions.Extensions; +using Vegasco.WebApi.Info; + +namespace WebApi.Tests.Integration.Info; + +[Collection(SharedTestCollection.Name)] +public class GetServerInfoTests +{ + private readonly WebAppFactory _factory; + + public GetServerInfoTests(WebAppFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetServerInfo_ShouldReturnServerInfo_WhenCalled() + { + // Arrange + + // Act + using HttpResponseMessage response = await _factory.HttpClient.GetAsync("/v1/info/server"); + + // Assert + response.IsSuccessStatusCode.Should().BeTrue(); + var serverInfo = await response.Content.ReadFromJsonAsync(); + serverInfo!.Environment.Should().NotBeEmpty(); + serverInfo.CommitDate.Should().BeAfter(23.August(2024)) + .And.NotBeAfter(DateTime.Now); + serverInfo.CommitId.Should().MatchRegex(@"[0-9a-f]{40}"); + serverInfo.FullVersion.Should().MatchRegex(@"\d\.\d\.\d(-[0-9a-zA-Z]+)?(\+g?[0-9a-f]{10})?"); + } +} \ No newline at end of file diff --git a/vegasco-server.sln b/vegasco-server.sln index f91c064..7a363cd 100644 --- a/vegasco-server.sln +++ b/vegasco-server.sln @@ -10,6 +10,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A16251C2-47DB-4017-812B-CA18B280E049}" ProjectSection(SolutionItems) = preProject README.md = README.md + version.json = version.json EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{437DE053-1DAB-4EEF-BEA6-E3B5179692F8}" diff --git a/version.json b/version.json new file mode 100644 index 0000000..4aa0bc2 --- /dev/null +++ b/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", + "version": "0.3", + "release": { + "firstUnstableTag": "alpha" + } +} \ No newline at end of file -- 2.49.1 From 2d79b5a0bf685d7ab6f9d8d92ed7815e0b1ed2c0 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 23 Aug 2024 18:56:01 +0200 Subject: [PATCH 023/150] Revert "Replace with gitea actions" This reverts commit 70f47b0dd136d85d5cd8a561a50f5665bc9ab613. # Conflicts: # .gitea/workflows/build.yaml # vegasco-server.sln --- .drone.yml | 66 +++++++++++++++++++++++++++++++++++++ .gitea/workflows/build.yaml | 65 ------------------------------------ vegasco-server.sln | 7 +--- 3 files changed, 67 insertions(+), 71 deletions(-) create mode 100644 .drone.yml delete mode 100644 .gitea/workflows/build.yaml diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..e84218b --- /dev/null +++ b/.drone.yml @@ -0,0 +1,66 @@ +kind: pipeline +type: docker +name: Pipeline + +trigger: + event: + include: + - push + - pull_request + - custom + +steps: + + - name: compile + image: mcr.microsoft.com/dotnet/sdk:8.0-alpine + environment: + CI_WORKSPACE: "/drone/src" + commands: + - dotnet build + volumes: + - name: dockersock + path: /var/run + + - name: test + image: quay.io/testcontainers/dind-drone-plugin + environment: + CI_WORKSPACE: "/drone/src" + settings: + build_image: mcr.microsoft.com/dotnet/sdk:8.0-alpine + cmd: + - dotnet test --no-build + volumes: + - name: dockersock + path: /var/run + depends_on: + - compile + + - name: Telegram notification + image: appleboy/drone-telegram + settings: + token: + from_secret: telegram_token + to: + from_secret: telegram_user_id + when: + status: + - success + - failure + depends_on: + - compile + - test + - docker build and push + - deploy to test + - deploy to prod + +services: + - name: docker + image: docker:dind + privileged: true + volumes: + - name: dockersock + path: /var/run + +volumes: + - name: dockersock + temp: { } diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml deleted file mode 100644 index d2ecb25..0000000 --- a/.gitea/workflows/build.yaml +++ /dev/null @@ -1,65 +0,0 @@ -name: Build Vegasco Server -on: - push: - pull_request: - workflow_run: - types: - - requested - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Install docker - uses: papodaca/install-docker-action@main - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '8.x' - - - name: Check out repository code - uses: actions/checkout@v3 - - - name: Build - run: dotnet build - - - name: Test - run: dotnet test --filter FullyQualifiedName!~System - - ### Report via telegram - - - name: Get 8 characters of commit id - uses: bhowell2/github-substring-action@1.0.2 - id: commit-hash-short - if: ${{ always() }} - with: - value: ${{ gitea.event.commits[0].id }} - length_from_start: 8 - - - name: Clean commit message - uses: mad9000/actions-find-and-replace-string@3 - id: commit-message-clean - with: - source: ${{ gitea.event.commits[0].message }} - find: '\n' - replace: '' - - - name: Telegram notification - uses: appleboy/telegram-action@master - if: ${{ always() }} - env: - STATUS_ICON: ${{ job.status == 'success' && '✅' || job.status == 'failure' && '❌' || '❕' }} - with: - to: ${{ secrets.TELEGRAM_TO }} - token: ${{ secrets.TELEGRAM_TOKEN }} - format: markdown - message: | - ${{ env.STATUS_ICON }} Build #${{ gitea.run_number }} of `${{ gitea.repository }}`: ${{ job.status }} - - 📝 Commit by ${{ gitea.actor }} on `${{ gitea.ref_name }}`: - [${{ steps.commit-message-clean.outputs.value }} - ${{ steps.commit-hash-short.outputs.substring }}](${{ gitea.event.commits[0].url }}) - - 🌐 ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_number }} diff --git a/vegasco-server.sln b/vegasco-server.sln index 7a363cd..30301fb 100644 --- a/vegasco-server.sln +++ b/vegasco-server.sln @@ -10,6 +10,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A16251C2-47DB-4017-812B-CA18B280E049}" ProjectSection(SolutionItems) = preProject README.md = README.md + .drone.yml = .drone.yml version.json = version.json EndProjectSection EndProject @@ -21,11 +22,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests.Integration", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.System", "tests\WebApi.Tests.System\WebApi.Tests.System.csproj", "{21418359-4A20-4F4A-B26C-A75A2B70AA10}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gitea_workflows", "gitea_workflows", "{B149FCA4-4FA0-4987-A451-75782BB0BD02}" - ProjectSection(SolutionItems) = preProject - .gitea\workflows\build.yaml = .gitea\workflows\build.yaml - EndProjectSection -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,7 +53,6 @@ Global {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} {21418359-4A20-4F4A-B26C-A75A2B70AA10} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} - {B149FCA4-4FA0-4987-A451-75782BB0BD02} = {A16251C2-47DB-4017-812B-CA18B280E049} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7813E32D-AE19-479C-853B-063882D2D05A} -- 2.49.1 From 89afc435fcab30100cce1da116075a13e10183d8 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 23 Aug 2024 19:02:01 +0200 Subject: [PATCH 024/150] Remove broken dependencies --- .drone.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.drone.yml b/.drone.yml index e84218b..8ecc2ac 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,16 +1,15 @@ kind: pipeline type: docker -name: Pipeline +name: Build and test trigger: - event: + event: include: - push - pull_request - custom steps: - - name: compile image: mcr.microsoft.com/dotnet/sdk:8.0-alpine environment: @@ -20,7 +19,7 @@ steps: volumes: - name: dockersock path: /var/run - + - name: test image: quay.io/testcontainers/dind-drone-plugin environment: @@ -49,9 +48,6 @@ steps: depends_on: - compile - test - - docker build and push - - deploy to test - - deploy to prod services: - name: docker -- 2.49.1 From ad9391093d4dc82975a26a22b513db7737b55a60 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 24 Aug 2024 12:28:46 +0200 Subject: [PATCH 025/150] Update nbgv version config --- version.json | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/version.json b/version.json index 4aa0bc2..656cc93 100644 --- a/version.json +++ b/version.json @@ -1,7 +1,10 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "0.3", - "release": { - "firstUnstableTag": "alpha" - } + "version": "2.0-beta", + "cloudBuild": { + "setAllVariables": true + }, + "publicReleaseRefSpec": [ + "prod" + ] } \ No newline at end of file -- 2.49.1 From 4855336c33da78048e7b82aca2a28c88131377cb Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 24 Aug 2024 12:33:19 +0200 Subject: [PATCH 026/150] Add docker build and push to pipeline --- .drone.yml | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 8ecc2ac..8114b38 100644 --- a/.drone.yml +++ b/.drone.yml @@ -33,7 +33,33 @@ steps: path: /var/run depends_on: - compile - + + - name: docker build and push + image: docker:24.0.7 + commands: + - docker build . -t $docker_registry$docker_repo:$DRONE_BRANCH + - echo $docker_password | docker login --username $docker_username --password-stdin $docker_registry + - docker push $docker_registry$docker_repo:$DRONE_BRANCH + environment: + docker_username: + from_secret: docker_username + docker_password: + from_secret: docker_password + docker_repo: + from_secret: docker_repo + docker_registry: + from_secret: docker_registry + volumes: + - name: dockersock + path: /var/run + when: + branch: + - main + - prod + depends_on: + - compile + - test + - name: Telegram notification image: appleboy/drone-telegram settings: -- 2.49.1 From ea689bb7a132f39b8312b3debd2cb605b2d29783 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 24 Aug 2024 12:39:30 +0200 Subject: [PATCH 027/150] Only build docker image on main For now --- .drone.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 8114b38..ac34db5 100644 --- a/.drone.yml +++ b/.drone.yml @@ -55,7 +55,6 @@ steps: when: branch: - main - - prod depends_on: - compile - test -- 2.49.1 From 036f4d1dfcd6f5e9cc5630520686894080306418 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 24 Aug 2024 12:46:27 +0200 Subject: [PATCH 028/150] Fix Dockerfile for nbgv --- .dockerignore | 2 +- src/WebApi/Dockerfile => Dockerfile | 4 ++-- vegasco-server.sln | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) rename src/WebApi/Dockerfile => Dockerfile (92%) diff --git a/.dockerignore b/.dockerignore index fe1152b..8f15cb2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,7 @@ **/.classpath **/.dockerignore **/.env -**/.git +#**/.git **/.gitignore **/.project **/.settings diff --git a/src/WebApi/Dockerfile b/Dockerfile similarity index 92% rename from src/WebApi/Dockerfile rename to Dockerfile index 98c2413..73deb8d 100644 --- a/src/WebApi/Dockerfile +++ b/Dockerfile @@ -9,9 +9,9 @@ EXPOSE 8081 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["WebApi.csproj", "src/WebApi/"] +COPY ["src/WebApi/WebApi.csproj", "src/WebApi/"] RUN dotnet restore "./src/WebApi/WebApi.csproj" -COPY . src/WebApi +COPY . . WORKDIR "/src/src/WebApi" RUN dotnet build "./WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/build diff --git a/vegasco-server.sln b/vegasco-server.sln index 30301fb..87881e8 100644 --- a/vegasco-server.sln +++ b/vegasco-server.sln @@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md .drone.yml = .drone.yml version.json = version.json + Dockerfile = Dockerfile EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{437DE053-1DAB-4EEF-BEA6-E3B5179692F8}" -- 2.49.1 From f410f69e9d65a3805a175afbc18cd87812468a04 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 24 Aug 2024 12:46:33 +0200 Subject: [PATCH 029/150] Only report failures --- .drone.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index ac34db5..2cf55c3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -67,8 +67,7 @@ steps: to: from_secret: telegram_user_id when: - status: - - success + status: - failure depends_on: - compile -- 2.49.1 From 88090878ee70b25b3a1ae18456efa1773ea516b2 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 24 Aug 2024 12:55:25 +0200 Subject: [PATCH 030/150] Include docker job in notification --- .drone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.drone.yml b/.drone.yml index 2cf55c3..b6d41f9 100644 --- a/.drone.yml +++ b/.drone.yml @@ -72,6 +72,7 @@ steps: depends_on: - compile - test + - docker build and push services: - name: docker -- 2.49.1 From d3d3675e3d56a011fc7de7fd54a8a021a935b7f2 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 24 Aug 2024 13:20:29 +0200 Subject: [PATCH 031/150] Fix onfiguration documentation in README --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8adf979..1c1b0e7 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,12 @@ Backend for the vegasco (***VE***hicle ***GAS*** ***CO***nsumption) application. ### Configuration -| Configuration | Description | Default | Required | -| --- | --- | --- | --- | -| JWT:Authority | The authority of the JWT token. | - | true | -| JWT:Audience | The audience of the JWT token. | - | true | -| JWT:Issuer | The issuer of the JWT token. | - | true | -| JWT:NameClaimType | The type of the name claim. | `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name` (C# constant `ClaimTypes.Name` | false | +| Configuration | Description | Default | Required | +|--------------------------|-------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|----------| +| JWT:MetadataUrl | The oidc meta data url | - | true | +| JWT:ValidAudience | The valid audience of the JWT token. | - | true | +| JWT:NameClaimType | The claim type of the user's name claim. | http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name | false | +| JWT:AllowHttpMetadataUrl | Whether to allow the meta data url to have http as protocol. Always true when `ASPNETCORE_ENVIRONMENT=true` | false | false | The application uses the prefix `Vegasco_` for environment variable names. The prefix is removed when the application reads the environment variables and duplicate entries are overwritten by the environment variables. -- 2.49.1 From 4a1f1a5a670f52fb23b2256970cedd4d6e7cab32 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 24 Aug 2024 13:43:43 +0200 Subject: [PATCH 032/150] Apply migrations on startup --- .../Common/DependencyInjectionExtensions.cs | 2 ++ .../Persistence/ApplyMigrationsService.cs | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 src/WebApi/Persistence/ApplyMigrationsService.cs diff --git a/src/WebApi/Common/DependencyInjectionExtensions.cs b/src/WebApi/Common/DependencyInjectionExtensions.cs index 7e09813..8c1326a 100644 --- a/src/WebApi/Common/DependencyInjectionExtensions.cs +++ b/src/WebApi/Common/DependencyInjectionExtensions.cs @@ -45,6 +45,8 @@ public static class DependencyInjectionExtensions services.AddHttpContextAccessor(); + services.AddHostedService(); + return services; } diff --git a/src/WebApi/Persistence/ApplyMigrationsService.cs b/src/WebApi/Persistence/ApplyMigrationsService.cs new file mode 100644 index 0000000..a55de3a --- /dev/null +++ b/src/WebApi/Persistence/ApplyMigrationsService.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore; + +namespace Vegasco.WebApi.Persistence; + +public class ApplyMigrationsService : IHostedService +{ + private readonly IServiceScopeFactory _scopeFactory; + + public ApplyMigrationsService(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + using IServiceScope scope = _scopeFactory.CreateScope(); + await using var dbContext = scope.ServiceProvider.GetRequiredService(); + await dbContext.Database.MigrateAsync(cancellationToken); + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; +} \ No newline at end of file -- 2.49.1 From 6b422545d95798490b7f4c548e2d5f14e994ae15 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 24 Aug 2024 13:43:58 +0200 Subject: [PATCH 033/150] Remove migrations sql script --- migrations/migration.sql | 71 ---------------------------------------- 1 file changed, 71 deletions(-) delete mode 100644 migrations/migration.sql diff --git a/migrations/migration.sql b/migrations/migration.sql deleted file mode 100644 index beeda51..0000000 --- a/migrations/migration.sql +++ /dev/null @@ -1,71 +0,0 @@ -CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( - "MigrationId" character varying(150) NOT NULL, - "ProductVersion" character varying(32) NOT NULL, - CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId") -); - -START TRANSACTION; - - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN - CREATE TABLE "Users" ( - "Id" text NOT NULL, - CONSTRAINT "PK_Users" PRIMARY KEY ("Id") - ); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN - CREATE TABLE "Cars" ( - "Id" uuid NOT NULL, - "Name" character varying(50) NOT NULL, - "UserId" text NOT NULL, - CONSTRAINT "PK_Cars" PRIMARY KEY ("Id"), - CONSTRAINT "FK_Cars_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "Users" ("Id") ON DELETE CASCADE - ); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN - CREATE TABLE "Consumptions" ( - "Id" uuid NOT NULL, - "DateTime" timestamp with time zone NOT NULL, - "Distance" double precision NOT NULL, - "Amount" double precision NOT NULL, - "IgnoreInCalculation" boolean NOT NULL, - "CarId" uuid NOT NULL, - CONSTRAINT "PK_Consumptions" PRIMARY KEY ("Id"), - CONSTRAINT "FK_Consumptions_Cars_CarId" FOREIGN KEY ("CarId") REFERENCES "Cars" ("Id") ON DELETE CASCADE - ); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN - CREATE INDEX "IX_Cars_UserId" ON "Cars" ("UserId"); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN - CREATE INDEX "IX_Consumptions_CarId" ON "Consumptions" ("CarId"); - END IF; -END $EF$; - -DO $EF$ -BEGIN - IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN - INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") - VALUES ('20240818105918_Initial', '8.0.8'); - END IF; -END $EF$; -COMMIT; - -- 2.49.1 From de7e9a71317d937a3b65496349cfd5ad8ad430a8 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 24 Aug 2024 13:44:23 +0200 Subject: [PATCH 034/150] Tweak log levels for non-dev environments --- src/WebApi/appsettings.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/WebApi/appsettings.json b/src/WebApi/appsettings.json index 10f68b8..fb6c195 100644 --- a/src/WebApi/appsettings.json +++ b/src/WebApi/appsettings.json @@ -1,8 +1,9 @@ { "Logging": { "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Default": "Warning", + "Vegasco": "Information", + "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*" -- 2.49.1 From 92e91de9c286833cf67ee53faec81dd97c66a3e1 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 24 Aug 2024 13:50:03 +0200 Subject: [PATCH 035/150] Include healthcheck in Dockerfile --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 73deb8d..2058b2c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ USER app WORKDIR /app EXPOSE 8080 EXPOSE 8081 +RUN apt-get update && apt-get install -y curl FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build ARG BUILD_CONFIGURATION=Release @@ -22,4 +23,5 @@ RUN dotnet publish "./WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p: FROM base AS final WORKDIR /app COPY --from=publish /app/publish . +HEALTHCHECK --interval=20s --timeout=1s --start-period=10s --retries=3 CMD curl --fail http://localhost:8080/health || exit 1 ENTRYPOINT ["dotnet", "WebApi.dll"] \ No newline at end of file -- 2.49.1 From d0704aea12e82f7b1b88a9eeaec089c0f8bd68fa Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 24 Aug 2024 14:24:18 +0200 Subject: [PATCH 036/150] Fix permissions in Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2058b2c..4e01f84 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ #See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base -USER app WORKDIR /app EXPOSE 8080 EXPOSE 8081 RUN apt-get update && apt-get install -y curl +USER app FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build ARG BUILD_CONFIGURATION=Release -- 2.49.1 From 4db35dbdb536fb4778aa81fd3aefdb47d3f27775 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 24 Aug 2024 14:26:38 +0200 Subject: [PATCH 037/150] Remove unnecessary migrations in integration tests --- tests/WebApi.Tests.Integration/WebAppFactory.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/WebApi.Tests.Integration/WebAppFactory.cs b/tests/WebApi.Tests.Integration/WebAppFactory.cs index f5abe9f..cd057b4 100644 --- a/tests/WebApi.Tests.Integration/WebAppFactory.cs +++ b/tests/WebApi.Tests.Integration/WebAppFactory.cs @@ -33,10 +33,6 @@ public sealed class WebAppFactory : WebApplicationFactory, IAsync // Force application startup (i.e. initialization and validation) _ = CreateClient(); - using var scope = Services.CreateScope(); - await using var dbContext = scope.ServiceProvider.GetRequiredService(); - await dbContext.Database.MigrateAsync(); - _postgresRespawner = await PostgresRespawner.CreateAsync(_database.GetConnectionString()); } -- 2.49.1 From 351a1a4635a7532f38587faf9c9c1cb9f92fc828 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 25 Aug 2024 13:14:32 +0200 Subject: [PATCH 038/150] Fix nested classes in open api document --- src/WebApi/Common/DependencyInjectionExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/WebApi/Common/DependencyInjectionExtensions.cs b/src/WebApi/Common/DependencyInjectionExtensions.cs index 8c1326a..03a9261 100644 --- a/src/WebApi/Common/DependencyInjectionExtensions.cs +++ b/src/WebApi/Common/DependencyInjectionExtensions.cs @@ -73,6 +73,7 @@ public static class DependencyInjectionExtensions .TrimStart('.'); } + fullClassName = fullClassName.Replace('+', '_'); return fullClassName; }); }); -- 2.49.1 From 136dd2311dd928b7aeab44445d66a37956c8de2f Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 25 Aug 2024 13:14:54 +0200 Subject: [PATCH 039/150] Prevent local test db to disappear on docker restart --- Run-PostgresDb.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Run-PostgresDb.ps1 b/Run-PostgresDb.ps1 index 4ad1ab2..f96dca7 100644 --- a/Run-PostgresDb.ps1 +++ b/Run-PostgresDb.ps1 @@ -1 +1 @@ -docker run --rm -d -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres postgres:16.3-alpine \ No newline at end of file +docker run -d -p 5432:5432 --restart always --name vegasco-test-db -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres postgres:16.3-alpine \ No newline at end of file -- 2.49.1 From d6c75654b0b459cd48fd838b8fb5d8b34ac02161 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 25 Aug 2024 13:39:00 +0200 Subject: [PATCH 040/150] Use wrapper class for get all api endpoints To enable e.g. pagination in the future --- src/WebApi/Cars/GetCars.cs | 31 ++++++++-- src/WebApi/Consumptions/GetConsumptions.cs | 60 +++++++++++++------ .../Cars/GetCarsTests.cs | 16 ++--- .../Consumptions/GetConsumptionsTests.cs | 8 +-- 4 files changed, 80 insertions(+), 35 deletions(-) diff --git a/src/WebApi/Cars/GetCars.cs b/src/WebApi/Cars/GetCars.cs index 4bbeb1a..14d0c5e 100644 --- a/src/WebApi/Cars/GetCars.cs +++ b/src/WebApi/Cars/GetCars.cs @@ -1,27 +1,46 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Vegasco.WebApi.Persistence; namespace Vegasco.WebApi.Cars; public static class GetCars { - public record Response(Guid Id, string Name); + public class ApiResponse + { + public IEnumerable Cars { get; set; } = []; + } + + public record ResponseDto(Guid Id, string Name); + + public class Request + { + [FromQuery(Name = "page")] public int? Page { get; set; } + [FromQuery(Name = "pageSize")] public int? PageSize { get; set; } + } public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) { return builder .MapGet("cars", Endpoint) + .WithDescription("Returns all cars") .WithTags("Cars"); } - private static async Task Endpoint( + private static async Task> Endpoint( + [AsParameters] Request request, ApplicationDbContext dbContext, CancellationToken cancellationToken) { - var cars = await dbContext.Cars - .Select(x => new Response(x.Id.Value, x.Name)) + List cars = await dbContext.Cars + .Select(x => new ResponseDto(x.Id.Value, x.Name)) .ToListAsync(cancellationToken); - return TypedResults.Ok(cars); + var response = new ApiResponse + { + Cars = cars + }; + return TypedResults.Ok(response); } } diff --git a/src/WebApi/Consumptions/GetConsumptions.cs b/src/WebApi/Consumptions/GetConsumptions.cs index bc13cee..9168366 100644 --- a/src/WebApi/Consumptions/GetConsumptions.cs +++ b/src/WebApi/Consumptions/GetConsumptions.cs @@ -1,27 +1,53 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Vegasco.WebApi.Persistence; namespace Vegasco.WebApi.Consumptions; public static class GetConsumptions { - public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId); + public class ApiResponse + { + public IEnumerable Consumptions { get; set; } = []; + } - public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) - { - return builder - .MapGet("consumptions", Endpoint) - .WithTags("Consumptions"); - } + public record ResponseDto( + Guid Id, + DateTimeOffset DateTime, + double Distance, + double Amount, + bool IgnoreInCalculation, + Guid CarId); - private static async Task Endpoint( - ApplicationDbContext dbContext, - CancellationToken cancellationToken) - { - var consumptions = await dbContext.Consumptions - .Select(x => new Response(x.Id.Value, x.DateTime, x.Distance, x.Amount, x.IgnoreInCalculation, x.CarId.Value)) - .ToListAsync(cancellationToken); + public class Request + { + [FromQuery(Name = "page")] public int? Page { get; set; } + [FromQuery(Name = "pageSize")] public int? PageSize { get; set; } + } - return TypedResults.Ok(consumptions); - } + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapGet("consumptions", Endpoint) + .WithDescription("Returns all consumption entries") + .WithTags("Consumptions"); + } + + private static async Task> Endpoint( + [AsParameters] Request request, + ApplicationDbContext dbContext, + CancellationToken cancellationToken) + { + List consumptions = await dbContext.Consumptions + .Select(x => + new ResponseDto(x.Id.Value, x.DateTime, x.Distance, x.Amount, x.IgnoreInCalculation, x.CarId.Value)) + .ToListAsync(cancellationToken); + + var apiResponse = new ApiResponse + { + Consumptions = consumptions + }; + return TypedResults.Ok(apiResponse); + } } \ No newline at end of file diff --git a/tests/WebApi.Tests.Integration/Cars/GetCarsTests.cs b/tests/WebApi.Tests.Integration/Cars/GetCarsTests.cs index b94c864..5924713 100644 --- a/tests/WebApi.Tests.Integration/Cars/GetCarsTests.cs +++ b/tests/WebApi.Tests.Integration/Cars/GetCarsTests.cs @@ -23,12 +23,12 @@ public class GetCarsTests : IAsyncLifetime // Arrange // Act - var response = await _factory.HttpClient.GetAsync("v1/cars"); + using HttpResponseMessage response = await _factory.HttpClient.GetAsync("v1/cars"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); - var cars = await response.Content.ReadFromJsonAsync>(); - cars.Should().BeEmpty(); + var apiResponse = await response.Content.ReadFromJsonAsync(); + apiResponse!.Cars.Should().BeEmpty(); } [Fact] @@ -40,8 +40,8 @@ public class GetCarsTests : IAsyncLifetime const int numberOfCars = 5; for (var i = 0; i < numberOfCars; i++) { - var createCarRequest = _carFaker.CreateCarRequest(); - var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + CreateCar.Request createCarRequest = _carFaker.CreateCarRequest(); + HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); @@ -49,12 +49,12 @@ public class GetCarsTests : IAsyncLifetime } // Act - var response = await _factory.HttpClient.GetAsync("v1/cars"); + using HttpResponseMessage response = await _factory.HttpClient.GetAsync("v1/cars"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); - var cars = await response.Content.ReadFromJsonAsync>(); - cars.Should().BeEquivalentTo(createdCars); + var apiResponse = await response.Content.ReadFromJsonAsync(); + apiResponse!.Cars.Should().BeEquivalentTo(createdCars); } public Task InitializeAsync() => Task.CompletedTask; diff --git a/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionsTests.cs b/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionsTests.cs index 553879b..dc919ee 100644 --- a/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionsTests.cs +++ b/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionsTests.cs @@ -42,8 +42,8 @@ public class GetConsumptionsTests : IAsyncLifetime // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); - var consumptions = await response.Content.ReadFromJsonAsync>(); - consumptions.Should().BeEquivalentTo(createdConsumptions); + var apiResponse = await response.Content.ReadFromJsonAsync(); + apiResponse!.Consumptions.Should().BeEquivalentTo(createdConsumptions); } [Fact] @@ -56,8 +56,8 @@ public class GetConsumptionsTests : IAsyncLifetime // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); - var consumptions = await response.Content.ReadFromJsonAsync>(); - consumptions.Should().BeEmpty(); + var apiResponse = await response.Content.ReadFromJsonAsync(); + apiResponse!.Consumptions.Should().BeEmpty(); } private async Task CreateConsumptionAsync() -- 2.49.1 From 22f47f44617556919fa578b5db337a62ee1737ae Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 1 Dec 2024 19:26:54 +0100 Subject: [PATCH 041/150] Upgrade to .NET 9 and update nuget packages --- Dockerfile | 4 +-- src/WebApi/WebApi.csproj | 28 +++++++++++-------- .../WebApi.Tests.Integration.csproj | 20 +++++++------ .../WebApi.Tests.System.csproj | 10 +++++-- .../WebApi.Tests.Unit.csproj | 16 +++++++---- 5 files changed, 47 insertions(+), 31 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4e01f84..a20303b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,13 @@ #See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base WORKDIR /app EXPOSE 8080 EXPOSE 8081 RUN apt-get update && apt-get install -y curl USER app -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY ["src/WebApi/WebApi.csproj", "src/WebApi/"] diff --git a/src/WebApi/WebApi.csproj b/src/WebApi/WebApi.csproj index dcbadf5..b5f5941 100644 --- a/src/WebApi/WebApi.csproj +++ b/src/WebApi/WebApi.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable enable 4bf893d3-0c16-41ec-8b46-2768d841215d @@ -13,24 +13,28 @@ - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - + + + + - + - + + + + + diff --git a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj index c7764a0..79c5109 100644 --- a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj +++ b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable @@ -10,18 +10,18 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -36,4 +36,8 @@ + + + + diff --git a/tests/WebApi.Tests.System/WebApi.Tests.System.csproj b/tests/WebApi.Tests.System/WebApi.Tests.System.csproj index 4ab0a38..fddfb8d 100644 --- a/tests/WebApi.Tests.System/WebApi.Tests.System.csproj +++ b/tests/WebApi.Tests.System/WebApi.Tests.System.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable @@ -15,8 +15,8 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -27,4 +27,8 @@ + + + + diff --git a/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj b/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj index d72ac6c..f7153d5 100644 --- a/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj +++ b/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable @@ -14,11 +14,11 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -33,4 +33,8 @@ + + + + -- 2.49.1 From 0e065b58b7c20c5ba9f1b049c7de7d6e3ba9d793 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 27 Dec 2024 19:22:13 +0100 Subject: [PATCH 042/150] Update packages --- src/WebApi/WebApi.csproj | 8 ++++---- .../WebApi.Tests.Integration.csproj | 6 +++--- tests/WebApi.Tests.System/WebApi.Tests.System.csproj | 4 ++-- tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/WebApi/WebApi.csproj b/src/WebApi/WebApi.csproj index b5f5941..4a838d9 100644 --- a/src/WebApi/WebApi.csproj +++ b/src/WebApi/WebApi.csproj @@ -22,19 +22,19 @@ all - + - + - + - + diff --git a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj index 79c5109..03411a6 100644 --- a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj +++ b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj @@ -20,9 +20,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -37,7 +37,7 @@ - + diff --git a/tests/WebApi.Tests.System/WebApi.Tests.System.csproj b/tests/WebApi.Tests.System/WebApi.Tests.System.csproj index fddfb8d..a49cc70 100644 --- a/tests/WebApi.Tests.System/WebApi.Tests.System.csproj +++ b/tests/WebApi.Tests.System/WebApi.Tests.System.csproj @@ -17,7 +17,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -28,7 +28,7 @@ - + diff --git a/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj b/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj index f7153d5..e31c3b3 100644 --- a/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj +++ b/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj @@ -19,7 +19,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -34,7 +34,7 @@ - + -- 2.49.1 From 5d0a49632a900277313265782e9651273d913498 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 27 Dec 2024 19:25:19 +0100 Subject: [PATCH 043/150] Update .NET version in pipeline --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index b6d41f9..5f155cd 100644 --- a/.drone.yml +++ b/.drone.yml @@ -11,7 +11,7 @@ trigger: steps: - name: compile - image: mcr.microsoft.com/dotnet/sdk:8.0-alpine + image: mcr.microsoft.com/dotnet/sdk:9.0-alpine environment: CI_WORKSPACE: "/drone/src" commands: @@ -25,7 +25,7 @@ steps: environment: CI_WORKSPACE: "/drone/src" settings: - build_image: mcr.microsoft.com/dotnet/sdk:8.0-alpine + build_image: mcr.microsoft.com/dotnet/sdk:9.0-alpine cmd: - dotnet test --no-build volumes: -- 2.49.1 From 7a2c50cb9ae57d1a451bc0e7144f3aa653525372 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Dec 2024 14:22:21 +0100 Subject: [PATCH 044/150] Update vulnerable packages --- tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj index 03411a6..8b543ef 100644 --- a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj +++ b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj @@ -10,6 +10,7 @@ + all @@ -20,6 +21,7 @@ + -- 2.49.1 From 857863a4d8b5b94d9517609136ab1453bc163d48 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Dec 2024 14:23:27 +0100 Subject: [PATCH 045/150] Replace sln file with slnx --- vegasco-server.sln | 61 --------------------------------------------- vegasco-server.slnx | 16 ++++++++++++ 2 files changed, 16 insertions(+), 61 deletions(-) delete mode 100644 vegasco-server.sln create mode 100644 vegasco-server.slnx diff --git a/vegasco-server.sln b/vegasco-server.sln deleted file mode 100644 index 87881e8..0000000 --- a/vegasco-server.sln +++ /dev/null @@ -1,61 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "src\WebApi\WebApi.csproj", "{9FF3C98A-5085-4EBE-A980-DB2148B0C00A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C051A684-BD6A-43F2-B0CC-F3C2315D99E3}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A16251C2-47DB-4017-812B-CA18B280E049}" - ProjectSection(SolutionItems) = preProject - README.md = README.md - .drone.yml = .drone.yml - version.json = version.json - Dockerfile = Dockerfile - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{437DE053-1DAB-4EEF-BEA6-E3B5179692F8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests.Unit", "tests\WebApi.Tests.Unit\WebApi.Tests.Unit.csproj", "{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests.Integration", "tests\WebApi.Tests.Integration\WebApi.Tests.Integration.csproj", "{0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.System", "tests\WebApi.Tests.System\WebApi.Tests.System.csproj", "{21418359-4A20-4F4A-B26C-A75A2B70AA10}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Release|Any CPU.Build.0 = Release|Any CPU - {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.Build.0 = Release|Any CPU - {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Release|Any CPU.Build.0 = Release|Any CPU - {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {9FF3C98A-5085-4EBE-A980-DB2148B0C00A} = {C051A684-BD6A-43F2-B0CC-F3C2315D99E3} - {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} - {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} - {21418359-4A20-4F4A-B26C-A75A2B70AA10} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {7813E32D-AE19-479C-853B-063882D2D05A} - EndGlobalSection -EndGlobal diff --git a/vegasco-server.slnx b/vegasco-server.slnx new file mode 100644 index 0000000..3aaf43d --- /dev/null +++ b/vegasco-server.slnx @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + -- 2.49.1 From 918477fb3a0bc491acf21be8cb83a6b6d29b1275 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Dec 2024 14:29:23 +0100 Subject: [PATCH 046/150] Use prerelease docker images which work with slnx file --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 5f155cd..f62ba61 100644 --- a/.drone.yml +++ b/.drone.yml @@ -11,7 +11,7 @@ trigger: steps: - name: compile - image: mcr.microsoft.com/dotnet/sdk:9.0-alpine + image: mcr.microsoft.com/dotnet/nightly/sdk:9.0-alpine environment: CI_WORKSPACE: "/drone/src" commands: @@ -25,7 +25,7 @@ steps: environment: CI_WORKSPACE: "/drone/src" settings: - build_image: mcr.microsoft.com/dotnet/sdk:9.0-alpine + build_image: mcr.microsoft.com/dotnet/nightly/sdk:9.0-alpine cmd: - dotnet test --no-build volumes: -- 2.49.1 From cf1a086e311f604b4da624fa2cd5493416b7da00 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Dec 2024 14:32:34 +0100 Subject: [PATCH 047/150] Recover sln file and revert pipeline --- .drone.yml | 4 +-- vegasco-server.sln | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 vegasco-server.sln diff --git a/.drone.yml b/.drone.yml index f62ba61..5f155cd 100644 --- a/.drone.yml +++ b/.drone.yml @@ -11,7 +11,7 @@ trigger: steps: - name: compile - image: mcr.microsoft.com/dotnet/nightly/sdk:9.0-alpine + image: mcr.microsoft.com/dotnet/sdk:9.0-alpine environment: CI_WORKSPACE: "/drone/src" commands: @@ -25,7 +25,7 @@ steps: environment: CI_WORKSPACE: "/drone/src" settings: - build_image: mcr.microsoft.com/dotnet/nightly/sdk:9.0-alpine + build_image: mcr.microsoft.com/dotnet/sdk:9.0-alpine cmd: - dotnet test --no-build volumes: diff --git a/vegasco-server.sln b/vegasco-server.sln new file mode 100644 index 0000000..87881e8 --- /dev/null +++ b/vegasco-server.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "src\WebApi\WebApi.csproj", "{9FF3C98A-5085-4EBE-A980-DB2148B0C00A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C051A684-BD6A-43F2-B0CC-F3C2315D99E3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A16251C2-47DB-4017-812B-CA18B280E049}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + .drone.yml = .drone.yml + version.json = version.json + Dockerfile = Dockerfile + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{437DE053-1DAB-4EEF-BEA6-E3B5179692F8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests.Unit", "tests\WebApi.Tests.Unit\WebApi.Tests.Unit.csproj", "{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests.Integration", "tests\WebApi.Tests.Integration\WebApi.Tests.Integration.csproj", "{0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.System", "tests\WebApi.Tests.System\WebApi.Tests.System.csproj", "{21418359-4A20-4F4A-B26C-A75A2B70AA10}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Release|Any CPU.Build.0 = Release|Any CPU + {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.Build.0 = Release|Any CPU + {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Release|Any CPU.Build.0 = Release|Any CPU + {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {9FF3C98A-5085-4EBE-A980-DB2148B0C00A} = {C051A684-BD6A-43F2-B0CC-F3C2315D99E3} + {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} + {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} + {21418359-4A20-4F4A-B26C-A75A2B70AA10} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7813E32D-AE19-479C-853B-063882D2D05A} + EndGlobalSection +EndGlobal -- 2.49.1 From 854be19fd5b8640c8895f8cd71bde17d1d933636 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Dec 2024 15:55:34 +0100 Subject: [PATCH 048/150] Remove system tests --- tests/WebApi.Tests.System/ComposeService.cs | 166 -- tests/WebApi.Tests.System/Constants.cs | 13 - .../DockerComposeFixture.cs | 301 --- tests/WebApi.Tests.System/Dockerfile.keycloak | 8 - .../SharedTestCollection.cs | 7 - .../WebApi.Tests.System/SharedTestContext.cs | 11 - tests/WebApi.Tests.System/Test.cs | 57 - tests/WebApi.Tests.System/TokenResponse.cs | 9 - .../WebApi.Tests.System.csproj | 34 - tests/WebApi.Tests.System/compose.system.yaml | 72 - tests/WebApi.Tests.System/test-realm.json | 1960 ----------------- vegasco-server.slnx | 1 - 12 files changed, 2639 deletions(-) delete mode 100644 tests/WebApi.Tests.System/ComposeService.cs delete mode 100644 tests/WebApi.Tests.System/Constants.cs delete mode 100644 tests/WebApi.Tests.System/DockerComposeFixture.cs delete mode 100644 tests/WebApi.Tests.System/Dockerfile.keycloak delete mode 100644 tests/WebApi.Tests.System/SharedTestCollection.cs delete mode 100644 tests/WebApi.Tests.System/SharedTestContext.cs delete mode 100644 tests/WebApi.Tests.System/Test.cs delete mode 100644 tests/WebApi.Tests.System/TokenResponse.cs delete mode 100644 tests/WebApi.Tests.System/WebApi.Tests.System.csproj delete mode 100644 tests/WebApi.Tests.System/compose.system.yaml delete mode 100644 tests/WebApi.Tests.System/test-realm.json diff --git a/tests/WebApi.Tests.System/ComposeService.cs b/tests/WebApi.Tests.System/ComposeService.cs deleted file mode 100644 index 05483c4..0000000 --- a/tests/WebApi.Tests.System/ComposeService.cs +++ /dev/null @@ -1,166 +0,0 @@ -using Ductus.FluentDocker.Services; -using Ductus.FluentDocker.Services.Extensions; -using System.Diagnostics.CodeAnalysis; - -namespace WebApi.Tests.System; - -public abstract class ComposeService : IDisposable -{ - public string ServiceName { get; init; } - public string ServiceInternalPort { get; init; } - public string ServiceInternalProtocol { get; init; } - public string ServiceInternalPortAndProtocol => $"{ServiceInternalPort}/{ServiceInternalProtocol}"; - - private readonly ICompositeService _dockerService; - private readonly bool _isTestRunningInContainer; - - private IContainerService? _container; - private bool _hasCheckedForContainer; - - /// - /// Not null, if is true. - /// - public IContainerService? Container - { - get - { - if (_hasCheckedForContainer) - { - return _container; - } - - _container ??= _dockerService.Containers.First(x => x.Name == ServiceName); - _hasCheckedForContainer = true; - - return _container; - } - } - - [MemberNotNullWhen(returnValue: true, nameof(Container))] - public bool ContainerExists => Container is not null; - - public ComposeService( - ICompositeService dockerService, - bool isTestRunningInContainer, - string serviceName, - string serviceInternalPort, - string serviceInternalProtocol = "tcp") - { - _dockerService = dockerService; - _isTestRunningInContainer = isTestRunningInContainer; - ServiceName = serviceName; - ServiceInternalPort = serviceInternalPort; - ServiceInternalProtocol = serviceInternalProtocol; - } - - public string? GetServiceUrl() - { - if (!ContainerExists) - { - return null; - } - - return _isTestRunningInContainer - ? GetServiceUrlWhenRunningInsideContainer() - : GetUrlFromOutsideContainer(Container, ServiceInternalPortAndProtocol); - } - - private string GetServiceUrlWhenRunningInsideContainer() - { - return $"http://{ServiceName}:{ServiceInternalPort}"; - } - - private static string GetUrlFromOutsideContainer(IContainerService container, string portAndProto) - { - var ipEndpoint = container.ToHostExposedEndpoint(portAndProto); - return $"http://{ipEndpoint.Address}:{ipEndpoint.Port}"; - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _container?.Dispose(); - } - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } -} - -public sealed class AppContainer : ComposeService -{ - public AppContainer(ICompositeService dockerService, bool isTestRunningInContainer) - : base(dockerService, isTestRunningInContainer, "app", "8080") - { - } -} - -public sealed class LoginContainer : ComposeService -{ - public LoginContainer(ICompositeService dockerService, bool isTestRunningInContainer) - : base(dockerService, isTestRunningInContainer, "login", "8080") - { - } -} - - -public sealed class TestAppContainer : IDisposable -{ - private IContainerService? _testApplicationContainer; - private bool _hasCheckedForThisContainer; - public IContainerService? Container - { - get - { - if (!_hasCheckedForThisContainer) - { - _testApplicationContainer = _dockerHost.GetRunningContainers() - .FirstOrDefault(x => x.Id.StartsWith(Environment.MachineName)); - - if (_testApplicationContainer is not null) - { - // If the test is running inside a container (i.e. usually in a pipeline), we do not want to mess with the container, just release the resources held by this program - _testApplicationContainer.RemoveOnDispose = false; - _testApplicationContainer.StopOnDispose = false; - } - - _hasCheckedForThisContainer = true; - } - - return _testApplicationContainer; - } - } - - private bool _hasCheckedIfTestRunInContainer; - private bool _isTestRunningInContainer; - public bool IsTestRunningInContainer - { - get - { - if (!_hasCheckedIfTestRunInContainer) - { - _isTestRunningInContainer = Container is not null; - _hasCheckedIfTestRunInContainer = true; - } - - return _isTestRunningInContainer; - } - } - - - private readonly IHostService _dockerHost; - - public TestAppContainer(IHostService dockerHost) - { - _dockerHost = dockerHost; - } - - public void Dispose() - { - _testApplicationContainer?.Dispose(); - } -} \ No newline at end of file diff --git a/tests/WebApi.Tests.System/Constants.cs b/tests/WebApi.Tests.System/Constants.cs deleted file mode 100644 index 4f05ff6..0000000 --- a/tests/WebApi.Tests.System/Constants.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace WebApi.Tests.System; - -public static class Constants -{ - public static class Login - { - public const string ClientId = "vegasco"; - public const string ClientSecret = "siIgnkijkkIxeQ9BDNwnGGUb60S53QZh"; - public const string Username = "test.user"; - public const string Password = "T3sttest."; - public const string Realm = "development"; - } -} \ No newline at end of file diff --git a/tests/WebApi.Tests.System/DockerComposeFixture.cs b/tests/WebApi.Tests.System/DockerComposeFixture.cs deleted file mode 100644 index 8922f73..0000000 --- a/tests/WebApi.Tests.System/DockerComposeFixture.cs +++ /dev/null @@ -1,301 +0,0 @@ -using Ductus.FluentDocker.Builders; -using Ductus.FluentDocker.Extensions; -using Ductus.FluentDocker.Model.Common; -using Ductus.FluentDocker.Model.Compose; -using Ductus.FluentDocker.Services; -using Ductus.FluentDocker.Services.Extensions; - -namespace WebApi.Tests.System; - -public sealed class DockerComposeFixture : IDisposable -{ - private const string ComposeFileName = "compose.system.yaml"; - - private const string AppServiceName = "app"; - private const string AppServiceInternalPort = "8080"; - private const string AppServiceInternalPortAndProtocol = $"{AppServiceInternalPort}/tcp"; - - private const string LoginServiceName = "login"; - private const string LoginServiceInternalPort = "8080"; - private const string LoginServiceInternalPortAndProtocol = $"{LoginServiceInternalPort}/tcp"; - - private static readonly string ComposeFilePath = Path.GetFullPath(Path.Combine("../../..", ComposeFileName)); - - private readonly ICompositeService _dockerService; - - - private bool _hasCheckedForThisContainer; - private bool _hasCheckedIfTestRunInContainer; - - private IHostService? _host; - - private bool _isTestRunningInContainer; - private INetworkService? _networkService; - private IContainerService? _testApplicationContainer; - - public DockerComposeFixture() - { - _dockerService = GetDockerComposeServices(); - _dockerService.Start(); - AttachDockerNetworksIfRunningInContainer(); - } - - private IContainerService? _appContainer; - public IContainerService AppContainer - { - get - { - _appContainer ??= _dockerService.Containers.First(x => x.Name == AppServiceName); - return _appContainer; - } - } - - private IContainerService? _loginContainer; - public IContainerService LoginContainer - { - get - { - _loginContainer ??= _dockerService.Containers.First(x => x.Name == LoginServiceName); - return _loginContainer; - } - } - - public IContainerService? TestApplicationContainer - { - get - { - if (!_hasCheckedForThisContainer) - { - _testApplicationContainer = DockerHost.GetRunningContainers() - .FirstOrDefault(x => x.Id.StartsWith(Environment.MachineName)); - - if (_testApplicationContainer is not null) - { - // If the test is running inside a container (i.e. usually in a pipeline), we do not want to mess with the container, just release the resources held by this program - _testApplicationContainer.RemoveOnDispose = false; - _testApplicationContainer.StopOnDispose = false; - } - - _hasCheckedForThisContainer = true; - } - - return _testApplicationContainer; - } - } - - public IHostService DockerHost - { - get - { - var hosts = new Hosts().Discover(); - _host = hosts.FirstOrDefault(x => x.IsNative) ?? - hosts.FirstOrDefault(x => x.Name == "default") ?? - hosts.FirstOrDefault(); - - if (_host is null) throw new InvalidOperationException("No docker host found"); - - return _host; - } - } - - public bool IsTestRunningInContainer - { - get - { - if (!_hasCheckedIfTestRunInContainer) - { - _isTestRunningInContainer = TestApplicationContainer is not null; - _hasCheckedIfTestRunInContainer = true; - } - - return _isTestRunningInContainer; - } - } - - public void Dispose() - { - _networkService?.Dispose(); - - _testApplicationContainer?.Dispose(); - _appContainer?.Dispose(); - _loginContainer?.Dispose(); - - // Kill container because otherwise the _dockerService.Dispose() takes much longer - KillDockerComposeServices(); - - _dockerService.Dispose(); - - _host?.Dispose(); - } - - private ICompositeService GetDockerComposeServices() - { - var services = new Builder() - .UseContainer() - .UseCompose() - .AssumeComposeVersion(ComposeVersion.V2) - .FromFile((TemplateString)ComposeFilePath) - .ForceBuild() - .RemoveOrphans() - .Wait("app", WaitForApplicationToListenToRequests) - .Build(); - - return services; - } - - private int WaitForApplicationToListenToRequests(IContainerService container, int iteration) - { - const int maxTryCount = 15; - ArgumentOutOfRangeException.ThrowIfGreaterThan(iteration, maxTryCount); - - var isStarted = container.Logs().ReadToEnd().Reverse().Any(x => x.Contains("Now listening on:")); - return isStarted ? 0 : 500; - } - - private void AttachDockerNetworksIfRunningInContainer() - { - if (!IsTestRunningInContainer) return; - - var randomNetworkName = Guid.NewGuid().ToString("N"); - _networkService = DockerHost.CreateNetwork(randomNetworkName, removeOnDispose: true); - - _networkService.Attach(AppContainer, true, AppServiceName); - _networkService.Attach(TestApplicationContainer, true); - } - - public string GetAppUrl() - { - return IsTestRunningInContainer - ? GetAppUrlWhenRunningInsideContainer() - : GetUrlFromOutsideContainer(AppContainer, AppServiceInternalPortAndProtocol); - } - - private static string GetAppUrlWhenRunningInsideContainer() - { - return $"http://{AppServiceName}:{AppServiceInternalPort}"; - } - - public string GetLoginUrl() - { - return IsTestRunningInContainer - ? GetLoginUrlWhenRunningInsideContainer() - : GetUrlFromOutsideContainer(LoginContainer, LoginServiceInternalPortAndProtocol); - } - - private static string GetLoginUrlWhenRunningInsideContainer() - { - return $"http://{LoginServiceName}:{LoginServiceInternalPort}"; - } - - private static string GetUrlFromOutsideContainer(IContainerService container, string portAndProto) - { - var ipEndpoint = container.ToHostExposedEndpoint(portAndProto); - return $"http://{ipEndpoint.Address}:{ipEndpoint.Port}"; - } - - private void KillDockerComposeServices() - { - foreach (var container in _dockerService.Containers) container.Remove(true); - } -} - - -public sealed class DockerComposeFixture2 : IDisposable -{ - private const string ComposeFileName = "compose.system.yaml"; - - private static readonly string ComposeFilePath = Path.GetFullPath(Path.Combine("../../..", ComposeFileName)); - - private readonly ICompositeService _dockerService; - - private IHostService? _host; - - private INetworkService? _networkService; - - public AppContainer AppContainer { get; init; } - public LoginContainer LoginContainer { get; init; } - public TestAppContainer TestApplicationContainer { get; init; } - - public DockerComposeFixture2() - { - _dockerService = GetDockerComposeServices(); - _dockerService.Start(); - - TestApplicationContainer = new TestAppContainer(DockerHost); - AppContainer = new AppContainer(_dockerService, TestApplicationContainer.IsTestRunningInContainer); - LoginContainer = new LoginContainer(_dockerService, TestApplicationContainer.IsTestRunningInContainer); - - AttachDockerNetworksIfRunningInContainer(); - } - - public IHostService DockerHost - { - get - { - var hosts = new Hosts().Discover(); - _host = hosts.FirstOrDefault(x => x.IsNative) ?? - hosts.FirstOrDefault(x => x.Name == "default") ?? - hosts.FirstOrDefault(); - - if (_host is null) throw new InvalidOperationException("No docker host found"); - - return _host; - } - } - - public void Dispose() - { - _networkService?.Dispose(); - - TestApplicationContainer.Dispose(); - AppContainer.Dispose(); - LoginContainer.Dispose(); - - // Kill container because otherwise the _dockerService.Dispose() takes much longer - KillDockerComposeServices(); - - _dockerService.Dispose(); - - _host?.Dispose(); - } - - private ICompositeService GetDockerComposeServices() - { - var services = new Builder() - .UseContainer() - .UseCompose() - .AssumeComposeVersion(ComposeVersion.V2) - .FromFile((TemplateString)ComposeFilePath) - .ForceBuild() - .RemoveOrphans() - .Wait("app", WaitForApplicationToListenToRequests) - .Build(); - - return services; - } - - private int WaitForApplicationToListenToRequests(IContainerService container, int iteration) - { - const int maxTryCount = 15; - ArgumentOutOfRangeException.ThrowIfGreaterThan(iteration, maxTryCount); - - var isStarted = container.Logs().ReadToEnd().Reverse().Any(x => x.Contains("Now listening on:")); - return isStarted ? 0 : 500; - } - - private void AttachDockerNetworksIfRunningInContainer() - { - if (!TestApplicationContainer.IsTestRunningInContainer) return; - - var randomNetworkName = Guid.NewGuid().ToString("N"); - _networkService = DockerHost.CreateNetwork(randomNetworkName, removeOnDispose: true); - - _networkService.Attach(AppContainer.Container!, true, AppContainer.ServiceName); - _networkService.Attach(TestApplicationContainer.Container, true); - } - - private void KillDockerComposeServices() - { - foreach (var container in _dockerService.Containers) container.Remove(true); - } -} \ No newline at end of file diff --git a/tests/WebApi.Tests.System/Dockerfile.keycloak b/tests/WebApi.Tests.System/Dockerfile.keycloak deleted file mode 100644 index c71c02f..0000000 --- a/tests/WebApi.Tests.System/Dockerfile.keycloak +++ /dev/null @@ -1,8 +0,0 @@ -FROM registry.access.redhat.com/ubi9 AS ubi-micro-build -RUN mkdir -p /mnt/rootfs -RUN dnf install --installroot /mnt/rootfs curl --releasever 9 --setopt install_weak_deps=false --nodocs -y && \ - dnf --installroot /mnt/rootfs clean all && \ - rpm --root /mnt/rootfs -e --nodeps setup - -FROM quay.io/keycloak/keycloak -COPY --from=ubi-micro-build /mnt/rootfs / \ No newline at end of file diff --git a/tests/WebApi.Tests.System/SharedTestCollection.cs b/tests/WebApi.Tests.System/SharedTestCollection.cs deleted file mode 100644 index 94787b7..0000000 --- a/tests/WebApi.Tests.System/SharedTestCollection.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace WebApi.Tests.System; - -[CollectionDefinition(Name)] -public class SharedTestCollection : ICollectionFixture -{ - public const string Name = nameof(SharedTestCollection); -} diff --git a/tests/WebApi.Tests.System/SharedTestContext.cs b/tests/WebApi.Tests.System/SharedTestContext.cs deleted file mode 100644 index aca0b6d..0000000 --- a/tests/WebApi.Tests.System/SharedTestContext.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace WebApi.Tests.System; - -public sealed class SharedTestContext : IDisposable -{ - public DockerComposeFixture2 DockerComposeFixture { get; set; } = new(); - - public void Dispose() - { - DockerComposeFixture.Dispose(); - } -} diff --git a/tests/WebApi.Tests.System/Test.cs b/tests/WebApi.Tests.System/Test.cs deleted file mode 100644 index 38aaf15..0000000 --- a/tests/WebApi.Tests.System/Test.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Net.Http.Headers; -using System.Text; -using System.Text.Json; - -namespace WebApi.Tests.System; - -[Collection(SharedTestCollection.Name)] -public class Test -{ - private readonly SharedTestContext _context; - - public Test(SharedTestContext context) - { - _context = context; - } - - //[Fact] - public async Task Test1() - { - var loginUrl = _context.DockerComposeFixture.LoginContainer.GetServiceUrl(); - var baseUrl = new Uri(loginUrl!, UriKind.Absolute); - var relativeUrl = new Uri($"/realms/{Constants.Login.Realm}/protocol/openid-connect/token", UriKind.Relative); - var uri = new Uri(baseUrl, relativeUrl); - var request = new HttpRequestMessage(HttpMethod.Post, uri); - - var data = new Dictionary - { - { "grant_type", "password" }, - { "audience", Constants.Login.ClientId }, - { "username", Constants.Login.Username }, - { "password", Constants.Login.Password } - }; - request.Content = new FormUrlEncodedContent(data); - - request.Headers.Authorization = new AuthenticationHeaderValue("Basic", - Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Constants.Login.ClientId}:{Constants.Login.ClientSecret}"))); - - using var client = new HttpClient(); - using var response = await client.SendAsync(request); - - var content = await response.Content.ReadAsStringAsync(); - var tokenResponse = JsonSerializer.Deserialize(content); - - var appUrl = _context.DockerComposeFixture.AppContainer.GetServiceUrl(); - baseUrl = new Uri(appUrl!, UriKind.Absolute); - relativeUrl = new Uri("/v1/cars", UriKind.Relative); - uri = new Uri(baseUrl, relativeUrl); - - request = new HttpRequestMessage(HttpMethod.Get, uri); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse!.AccessToken); - - using var response2 = await client.SendAsync(request); - - var content2 = await response2.Content.ReadAsStringAsync(); - - } -} \ No newline at end of file diff --git a/tests/WebApi.Tests.System/TokenResponse.cs b/tests/WebApi.Tests.System/TokenResponse.cs deleted file mode 100644 index 58b157a..0000000 --- a/tests/WebApi.Tests.System/TokenResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace WebApi.Tests.System; - -public class TokenResponse -{ - [JsonPropertyName("access_token")] - public required string AccessToken { get; init; } -} \ No newline at end of file diff --git a/tests/WebApi.Tests.System/WebApi.Tests.System.csproj b/tests/WebApi.Tests.System/WebApi.Tests.System.csproj deleted file mode 100644 index a49cc70..0000000 --- a/tests/WebApi.Tests.System/WebApi.Tests.System.csproj +++ /dev/null @@ -1,34 +0,0 @@ - - - - net9.0 - enable - enable - - false - true - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - diff --git a/tests/WebApi.Tests.System/compose.system.yaml b/tests/WebApi.Tests.System/compose.system.yaml deleted file mode 100644 index 9c15fa0..0000000 --- a/tests/WebApi.Tests.System/compose.system.yaml +++ /dev/null @@ -1,72 +0,0 @@ -services: - app: - build: ../../src/WebApi - environment: - Vegasco_ConnectionStrings__Default: "Host=db;Port=5432;Database=postgres;Username=postgres;Password=postgres" - Vegasco_JWT__MetadataUrl: http://login:8080/realms/development/.well-known/openid-configuration - Vegasco_JWT__ValidAudience: vegasco - Vegasco_JWT__NameClaimType: name - Vegasco_JWT__AllowHttpMetadataUrl: "true" - ports: - - "8080" - depends_on: - db: - condition: service_healthy - login: - condition: service_healthy - - db: - image: postgres:16.3-alpine - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - healthcheck: - test: pg_isready -d postgres - interval: 5s - timeout: 2s - retries: 3 - start_period: 5s - - login: - build: - context: . - dockerfile: Dockerfile.keycloak - command: start --import-realm - environment: - KC_DB: postgres - KC_DB_URL_HOST: login-db - KC_DB_URL_PORT: 5432 - KC_DB_URL_DATABASE: keycloak - KC_DB_USERNAME: keycloak - KC_DB_PASSWORD: keycloak - KEYCLOAK_ADMIN: admin - KEYCLOAK_ADMIN_PASSWORD: admin1! - KC_HOSTNAME_STRICT: false - KC_HEALTH_ENABLED: true - KC_METRICS_ENABLED: true - KC_HTTP_ENABLED: true - ports: - - "8080" - volumes: - - ./test-realm.json:/opt/keycloak/data/import/test-realm.json:ro - depends_on: - login-db: - condition: service_healthy - healthcheck: - test: curl --head -fsS http://localhost:9000/health/ready || exit 1 - interval: 5s - timeout: 2s - retries: 6 - start_period: 5s - - login-db: - image: postgres:16-alpine - environment: - POSTGRES_USER: keycloak - POSTGRES_PASSWORD: keycloak - healthcheck: - test: pg_isready -d keycloak - interval: 5s - timeout: 2s - retries: 3 - start_period: 5s diff --git a/tests/WebApi.Tests.System/test-realm.json b/tests/WebApi.Tests.System/test-realm.json deleted file mode 100644 index 46481f5..0000000 --- a/tests/WebApi.Tests.System/test-realm.json +++ /dev/null @@ -1,1960 +0,0 @@ -{ - "id" : "59058b68-b3dd-408a-a0cc-8be9ec080347", - "realm" : "development", - "notBefore" : 0, - "defaultSignatureAlgorithm" : "RS256", - "revokeRefreshToken" : false, - "refreshTokenMaxReuse" : 0, - "accessTokenLifespan" : 300, - "accessTokenLifespanForImplicitFlow" : 900, - "ssoSessionIdleTimeout" : 1800, - "ssoSessionMaxLifespan" : 36000, - "ssoSessionIdleTimeoutRememberMe" : 0, - "ssoSessionMaxLifespanRememberMe" : 0, - "offlineSessionIdleTimeout" : 2592000, - "offlineSessionMaxLifespanEnabled" : false, - "offlineSessionMaxLifespan" : 5184000, - "clientSessionIdleTimeout" : 0, - "clientSessionMaxLifespan" : 0, - "clientOfflineSessionIdleTimeout" : 0, - "clientOfflineSessionMaxLifespan" : 0, - "accessCodeLifespan" : 60, - "accessCodeLifespanUserAction" : 300, - "accessCodeLifespanLogin" : 1800, - "actionTokenGeneratedByAdminLifespan" : 43200, - "actionTokenGeneratedByUserLifespan" : 300, - "oauth2DeviceCodeLifespan" : 600, - "oauth2DevicePollingInterval" : 5, - "enabled" : true, - "sslRequired" : "external", - "registrationAllowed" : false, - "registrationEmailAsUsername" : false, - "rememberMe" : false, - "verifyEmail" : false, - "loginWithEmailAllowed" : true, - "duplicateEmailsAllowed" : false, - "resetPasswordAllowed" : false, - "editUsernameAllowed" : false, - "bruteForceProtected" : false, - "permanentLockout" : false, - "maxTemporaryLockouts" : 0, - "maxFailureWaitSeconds" : 900, - "minimumQuickLoginWaitSeconds" : 60, - "waitIncrementSeconds" : 60, - "quickLoginCheckMilliSeconds" : 1000, - "maxDeltaTimeSeconds" : 43200, - "failureFactor" : 30, - "roles" : { - "realm" : [ { - "id" : "99223865-08d1-446f-986d-2fb8cff0730b", - "name" : "offline_access", - "description" : "${role_offline-access}", - "composite" : false, - "clientRole" : false, - "containerId" : "59058b68-b3dd-408a-a0cc-8be9ec080347", - "attributes" : { } - }, { - "id" : "f9f1c2d3-dd92-4527-82d1-5d4a9b2ced66", - "name" : "default-roles-development", - "description" : "${role_default-roles}", - "composite" : true, - "composites" : { - "realm" : [ "offline_access", "uma_authorization" ], - "client" : { - "account" : [ "view-profile", "manage-account" ] - } - }, - "clientRole" : false, - "containerId" : "59058b68-b3dd-408a-a0cc-8be9ec080347", - "attributes" : { } - }, { - "id" : "8986fd0e-4c10-4fb7-a8f6-83e408c29e95", - "name" : "uma_authorization", - "description" : "${role_uma_authorization}", - "composite" : false, - "clientRole" : false, - "containerId" : "59058b68-b3dd-408a-a0cc-8be9ec080347", - "attributes" : { } - } ], - "client" : { - "realm-management" : [ { - "id" : "5964f7e7-3702-4499-b3f6-7aa1d3e80f11", - "name" : "create-client", - "description" : "${role_create-client}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "4f2daa75-e42b-41bb-aa10-22aa64936a93", - "name" : "realm-admin", - "description" : "${role_realm-admin}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "create-client", "manage-authorization", "manage-users", "view-authorization", "view-users", "manage-identity-providers", "impersonation", "manage-realm", "view-realm", "query-users", "manage-events", "query-clients", "query-groups", "manage-clients", "view-clients", "view-identity-providers", "query-realms", "view-events" ] - } - }, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "f64e7de4-fc10-491c-8f7f-817b70c53cbc", - "name" : "manage-authorization", - "description" : "${role_manage-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "64bdb656-58fc-4059-83f5-6af88e0d2d94", - "name" : "manage-users", - "description" : "${role_manage-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "9acf79b8-6026-426a-b788-078833c96046", - "name" : "view-authorization", - "description" : "${role_view-authorization}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "00172343-72d7-46f8-b1bb-30c98d062335", - "name" : "view-users", - "description" : "${role_view-users}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "query-users", "query-groups" ] - } - }, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "aaf57855-d18e-4ebf-a89f-8479d6584c78", - "name" : "manage-identity-providers", - "description" : "${role_manage-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "843a9a47-a4d6-46b7-bd15-56134419df36", - "name" : "impersonation", - "description" : "${role_impersonation}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "83d6dabe-adf4-4921-b84f-0cb6d67c8524", - "name" : "manage-realm", - "description" : "${role_manage-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "4f8fbeb9-2150-48d3-9ff0-fadb9f952dec", - "name" : "view-realm", - "description" : "${role_view-realm}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "da835590-2ff2-47a3-8eda-9ddd27f1f55e", - "name" : "query-users", - "description" : "${role_query-users}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "9dd8eac9-f0b8-47eb-aa16-fdbcb716ffa8", - "name" : "manage-events", - "description" : "${role_manage-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "7fdafa52-5875-4f1c-bc2f-bcfe56214329", - "name" : "query-clients", - "description" : "${role_query-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "9d4ecec1-6b73-42dc-a7ec-fc9b89e35cec", - "name" : "query-groups", - "description" : "${role_query-groups}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "125ca24f-fde8-4340-9c59-ff8b605a89e6", - "name" : "manage-clients", - "description" : "${role_manage-clients}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "7a45ba95-b3e3-4b84-a1d4-1b725a182667", - "name" : "view-clients", - "description" : "${role_view-clients}", - "composite" : true, - "composites" : { - "client" : { - "realm-management" : [ "query-clients" ] - } - }, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "ecf9933f-de72-4917-8553-7e18332d217d", - "name" : "view-identity-providers", - "description" : "${role_view-identity-providers}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "c51ed824-8455-4584-883e-135d9af5ee4b", - "name" : "query-realms", - "description" : "${role_query-realms}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - }, { - "id" : "5c72a2e9-bc45-4ae0-a5cf-d09e70c1b61c", - "name" : "view-events", - "description" : "${role_view-events}", - "composite" : false, - "clientRole" : true, - "containerId" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "attributes" : { } - } ], - "vegasco" : [ ], - "security-admin-console" : [ ], - "admin-cli" : [ ], - "account-console" : [ ], - "broker" : [ { - "id" : "386764da-c0c3-46c8-ae18-518eff6b6b84", - "name" : "read-token", - "description" : "${role_read-token}", - "composite" : false, - "clientRole" : true, - "containerId" : "1f8df246-a2f8-4f21-8c20-eda7dcdae2b6", - "attributes" : { } - } ], - "account" : [ { - "id" : "c6e146aa-1a5c-4fcc-9a65-7033c5ec1c95", - "name" : "manage-account-links", - "description" : "${role_manage-account-links}", - "composite" : false, - "clientRole" : true, - "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes" : { } - }, { - "id" : "3141a4ac-31b6-4eb4-9b13-29ea1317b721", - "name" : "manage-consent", - "description" : "${role_manage-consent}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "view-consent" ] - } - }, - "clientRole" : true, - "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes" : { } - }, { - "id" : "7f7e8345-2e96-40af-94c8-f1b4e3d0314a", - "name" : "view-profile", - "description" : "${role_view-profile}", - "composite" : false, - "clientRole" : true, - "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes" : { } - }, { - "id" : "18e3cd14-4705-4f5d-ab70-c368cab6434e", - "name" : "delete-account", - "description" : "${role_delete-account}", - "composite" : false, - "clientRole" : true, - "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes" : { } - }, { - "id" : "68a0330a-0c0a-480c-80b4-2d7a11905741", - "name" : "view-groups", - "description" : "${role_view-groups}", - "composite" : false, - "clientRole" : true, - "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes" : { } - }, { - "id" : "7ee101f9-fab5-4cf2-ab0c-f8d8b2eea394", - "name" : "manage-account", - "description" : "${role_manage-account}", - "composite" : true, - "composites" : { - "client" : { - "account" : [ "manage-account-links" ] - } - }, - "clientRole" : true, - "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes" : { } - }, { - "id" : "c0b24a71-4f3c-4ed1-9eee-84e3cbba9adb", - "name" : "view-consent", - "description" : "${role_view-consent}", - "composite" : false, - "clientRole" : true, - "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes" : { } - }, { - "id" : "e06cb945-b1d9-4013-ba54-c5e72ad08d65", - "name" : "view-applications", - "description" : "${role_view-applications}", - "composite" : false, - "clientRole" : true, - "containerId" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "attributes" : { } - } ] - } - }, - "groups" : [ ], - "defaultRole" : { - "id" : "f9f1c2d3-dd92-4527-82d1-5d4a9b2ced66", - "name" : "default-roles-development", - "description" : "${role_default-roles}", - "composite" : true, - "clientRole" : false, - "containerId" : "59058b68-b3dd-408a-a0cc-8be9ec080347" - }, - "requiredCredentials" : [ "password" ], - "otpPolicyType" : "totp", - "otpPolicyAlgorithm" : "HmacSHA1", - "otpPolicyInitialCounter" : 0, - "otpPolicyDigits" : 6, - "otpPolicyLookAheadWindow" : 1, - "otpPolicyPeriod" : 30, - "otpPolicyCodeReusable" : false, - "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName" ], - "localizationTexts" : { }, - "webAuthnPolicyRpEntityName" : "keycloak", - "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], - "webAuthnPolicyRpId" : "", - "webAuthnPolicyAttestationConveyancePreference" : "not specified", - "webAuthnPolicyAuthenticatorAttachment" : "not specified", - "webAuthnPolicyRequireResidentKey" : "not specified", - "webAuthnPolicyUserVerificationRequirement" : "not specified", - "webAuthnPolicyCreateTimeout" : 0, - "webAuthnPolicyAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyAcceptableAaguids" : [ ], - "webAuthnPolicyExtraOrigins" : [ ], - "webAuthnPolicyPasswordlessRpEntityName" : "keycloak", - "webAuthnPolicyPasswordlessSignatureAlgorithms" : [ "ES256" ], - "webAuthnPolicyPasswordlessRpId" : "", - "webAuthnPolicyPasswordlessAttestationConveyancePreference" : "not specified", - "webAuthnPolicyPasswordlessAuthenticatorAttachment" : "not specified", - "webAuthnPolicyPasswordlessRequireResidentKey" : "not specified", - "webAuthnPolicyPasswordlessUserVerificationRequirement" : "not specified", - "webAuthnPolicyPasswordlessCreateTimeout" : 0, - "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister" : false, - "webAuthnPolicyPasswordlessAcceptableAaguids" : [ ], - "webAuthnPolicyPasswordlessExtraOrigins" : [ ], - "users" : [ { - "id" : "c5404b32-c20e-4af1-859b-18e2df6998a2", - "username" : "test.user", - "firstName" : "test", - "lastName" : "user", - "email" : "test.user@example.com", - "emailVerified" : true, - "createdTimestamp" : 1722885095042, - "enabled" : true, - "totp" : false, - "credentials" : [ { - "id" : "55a67bcf-b7df-4e10-840c-8cedc2e263af", - "type" : "password", - "createdDate" : 1722885095911, - "secretData" : "{\"value\":\"A9/c6FWaGkk7fC9qQmiiH3FlFFpWBjg9ZSvgnJIkd68=\",\"salt\":\"ec93soiRD3MWjohp8XWxfw==\",\"additionalParameters\":{}}", - "credentialData" : "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" - } ], - "disableableCredentialTypes" : [ ], - "requiredActions" : [ ], - "realmRoles" : [ "default-roles-development" ], - "notBefore" : 0, - "groups" : [ ] - } ], - "scopeMappings" : [ { - "clientScope" : "offline_access", - "roles" : [ "offline_access" ] - } ], - "clientScopeMappings" : { - "account" : [ { - "client" : "account-console", - "roles" : [ "manage-account", "view-groups" ] - } ] - }, - "clients" : [ { - "id" : "cc404b9b-7c8d-49f9-a299-f1bd4477193f", - "clientId" : "account", - "name" : "${client_account}", - "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/development/account/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/development/account/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "post.logout.redirect.uris" : "+" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "422b7172-a668-43e2-a00b-3f153793e4a1", - "name" : "docker-v2-allow-all-mapper", - "protocol" : "docker-v2", - "protocolMapper" : "docker-v2-allow-all-mapper", - "consentRequired" : false, - "config" : { } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "7c4aaabb-f092-4ace-9bc8-6f728336cf26", - "clientId" : "account-console", - "name" : "${client_account-console}", - "rootUrl" : "${authBaseUrl}", - "baseUrl" : "/realms/development/account/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/realms/development/account/*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "post.logout.redirect.uris" : "+", - "pkce.code.challenge.method" : "S256" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "a6215df7-0c5e-4347-ba79-4df4fb588b06", - "name" : "docker-v2-allow-all-mapper", - "protocol" : "docker-v2", - "protocolMapper" : "docker-v2-allow-all-mapper", - "consentRequired" : false, - "config" : { } - }, { - "id" : "5a0023ed-d354-4c0d-b8c6-a3eeada27299", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "2cbd4c50-560a-4a30-8dd8-ce69000ad431", - "clientId" : "admin-cli", - "name" : "${client_admin-cli}", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : false, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : true, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "post.logout.redirect.uris" : "+" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "a3476370-00d3-44f5-882f-6bf8cdcf64c5", - "name" : "docker-v2-allow-all-mapper", - "protocol" : "docker-v2", - "protocolMapper" : "docker-v2-allow-all-mapper", - "consentRequired" : false, - "config" : { } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "1f8df246-a2f8-4f21-8c20-eda7dcdae2b6", - "clientId" : "broker", - "name" : "${client_broker}", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : true, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "post.logout.redirect.uris" : "+" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "ad293b7e-096d-48a0-9ac9-27e357f50bdb", - "name" : "docker-v2-allow-all-mapper", - "protocol" : "docker-v2", - "protocolMapper" : "docker-v2-allow-all-mapper", - "consentRequired" : false, - "config" : { } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "beea0490-5673-465b-8bd9-2bb7dd546429", - "clientId" : "realm-management", - "name" : "${client_realm-management}", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : true, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : false, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "post.logout.redirect.uris" : "+" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "de0cbd5d-29f7-49e8-8f72-dccbea133782", - "name" : "docker-v2-allow-all-mapper", - "protocol" : "docker-v2", - "protocolMapper" : "docker-v2-allow-all-mapper", - "consentRequired" : false, - "config" : { } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "c8e21e7a-7616-4309-9061-793ffda8936a", - "clientId" : "security-admin-console", - "name" : "${client_security-admin-console}", - "rootUrl" : "${authAdminUrl}", - "baseUrl" : "/admin/development/console/", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "redirectUris" : [ "/admin/development/console/*" ], - "webOrigins" : [ "+" ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : false, - "serviceAccountsEnabled" : false, - "publicClient" : true, - "frontchannelLogout" : false, - "protocol" : "openid-connect", - "attributes" : { - "post.logout.redirect.uris" : "+", - "pkce.code.challenge.method" : "S256" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : 0, - "protocolMappers" : [ { - "id" : "f2f24f2c-7f00-4c1b-862c-a1d821965330", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - }, { - "id" : "14db6a94-fd3f-40b2-95a2-139e329e51cf", - "name" : "docker-v2-allow-all-mapper", - "protocol" : "docker-v2", - "protocolMapper" : "docker-v2-allow-all-mapper", - "consentRequired" : false, - "config" : { } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - }, { - "id" : "d6877a14-f114-453c-a88c-dbe2472e4ed8", - "clientId" : "vegasco", - "name" : "Vegasco", - "description" : "", - "rootUrl" : "http://localhost/", - "adminUrl" : "", - "baseUrl" : "", - "surrogateAuthRequired" : false, - "enabled" : true, - "alwaysDisplayInConsole" : false, - "clientAuthenticatorType" : "client-secret", - "secret" : "siIgnkijkkIxeQ9BDNwnGGUb60S53QZh", - "redirectUris" : [ "*" ], - "webOrigins" : [ ], - "notBefore" : 0, - "bearerOnly" : false, - "consentRequired" : false, - "standardFlowEnabled" : true, - "implicitFlowEnabled" : false, - "directAccessGrantsEnabled" : true, - "serviceAccountsEnabled" : false, - "publicClient" : false, - "frontchannelLogout" : true, - "protocol" : "openid-connect", - "attributes" : { - "oidc.ciba.grant.enabled" : "false", - "client.secret.creation.time" : "1723219692", - "backchannel.logout.session.required" : "true", - "post.logout.redirect.uris" : "*", - "display.on.consent.screen" : "false", - "oauth2.device.authorization.grant.enabled" : "false", - "backchannel.logout.revoke.offline.tokens" : "false" - }, - "authenticationFlowBindingOverrides" : { }, - "fullScopeAllowed" : false, - "nodeReRegistrationTimeout" : -1, - "protocolMappers" : [ { - "id" : "514219d4-0807-44c2-90e8-310634357c0e", - "name" : "Vegasco_Audience_Mapper", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-mapper", - "consentRequired" : false, - "config" : { - "included.client.audience" : "vegasco", - "id.token.claim" : "false", - "lightweight.claim" : "false", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "userinfo.token.claim" : "false" - } - }, { - "id" : "de5204df-ee72-4105-9640-cc01ddf08b18", - "name" : "docker-v2-allow-all-mapper", - "protocol" : "docker-v2", - "protocolMapper" : "docker-v2-allow-all-mapper", - "consentRequired" : false, - "config" : { } - } ], - "defaultClientScopes" : [ "web-origins", "acr", "roles", "profile", "basic", "email" ], - "optionalClientScopes" : [ "address", "phone", "offline_access", "microprofile-jwt" ] - } ], - "clientScopes" : [ { - "id" : "029c8cc4-a0b9-4c90-9f5f-63a408c7ee49", - "name" : "offline_access", - "description" : "OpenID Connect built-in scope: offline_access", - "protocol" : "openid-connect", - "attributes" : { - "consent.screen.text" : "${offlineAccessScopeConsentText}", - "display.on.consent.screen" : "true" - } - }, { - "id" : "daf886da-43dc-424d-a089-bb564085b128", - "name" : "roles", - "description" : "OpenID Connect scope for add user roles to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "consent.screen.text" : "${rolesScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "485ebff8-7f64-444a-aa59-446ab3e02c20", - "name" : "audience resolve", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-audience-resolve-mapper", - "consentRequired" : false, - "config" : { - "access.token.claim" : "true", - "introspection.token.claim" : "true" - } - }, { - "id" : "7e276de2-892a-457e-8437-0fa8d9029549", - "name" : "client roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-client-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "resource_access.${client_id}.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - }, { - "id" : "35b0b4fb-9f6d-4695-9cf6-c2fc99581d4c", - "name" : "realm roles", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "user.attribute" : "foo", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "realm_access.roles", - "jsonType.label" : "String", - "multivalued" : "true" - } - } ] - }, { - "id" : "167dfb37-d340-4fe9-ae50-6e7cbaac4f31", - "name" : "acr", - "description" : "OpenID Connect scope for add acr (authentication context class reference) to the token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "09fc2f1e-8c67-44f8-b9fd-a837db569332", - "name" : "acr loa level", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-acr-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "access.token.claim" : "true", - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true" - } - } ] - }, { - "id" : "c79fc776-2b2d-43d6-b12c-f57262311b6a", - "name" : "profile", - "description" : "OpenID Connect built-in scope: profile", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${profileScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "2cd6cd74-4a6a-4221-adc6-804b7227e1e5", - "name" : "birthdate", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "birthdate", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "birthdate", - "jsonType.label" : "String" - } - }, { - "id" : "0f7d36bb-7540-4063-8dcc-26c6408f6d94", - "name" : "nickname", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "nickname", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "nickname", - "jsonType.label" : "String" - } - }, { - "id" : "ce756133-7f49-43e9-8b32-ca358eddf877", - "name" : "profile", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "profile", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "profile", - "jsonType.label" : "String" - } - }, { - "id" : "1729b872-9632-4f52-8726-8d5a8e77d2ca", - "name" : "middle name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "middleName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "middle_name", - "jsonType.label" : "String" - } - }, { - "id" : "4db87e96-0bfd-4f05-91d1-ca675db6f74e", - "name" : "website", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "website", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "website", - "jsonType.label" : "String" - } - }, { - "id" : "0c589f3d-02ae-4584-b5d2-95c18c2c93be", - "name" : "gender", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "gender", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "gender", - "jsonType.label" : "String" - } - }, { - "id" : "985d5281-c952-47df-8ed4-6e34ba87e518", - "name" : "picture", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "picture", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "picture", - "jsonType.label" : "String" - } - }, { - "id" : "cd7d78c6-7eaa-4b92-8d70-d077d4bcee6c", - "name" : "family name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "lastName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "family_name", - "jsonType.label" : "String" - } - }, { - "id" : "5a0fd3a1-ab3a-4428-bb7c-de529a1021dc", - "name" : "updated at", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "updatedAt", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "updated_at", - "jsonType.label" : "long" - } - }, { - "id" : "7303fd26-77bc-45d6-8810-27e22706d3ff", - "name" : "given name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "firstName", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "given_name", - "jsonType.label" : "String" - } - }, { - "id" : "1b9ef4aa-bd49-444c-9ba0-aa2178237beb", - "name" : "full name", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-full-name-mapper", - "consentRequired" : false, - "config" : { - "id.token.claim" : "true", - "introspection.token.claim" : "true", - "access.token.claim" : "true", - "userinfo.token.claim" : "true" - } - }, { - "id" : "aba2eec9-c04b-453a-95b7-001700e91eed", - "name" : "username", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "preferred_username", - "jsonType.label" : "String" - } - }, { - "id" : "cd17207d-9f14-4c36-991d-55eb4ef2825e", - "name" : "zoneinfo", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "zoneinfo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "zoneinfo", - "jsonType.label" : "String" - } - }, { - "id" : "4aa38a97-aaaf-49bc-9f50-4b6c85bf8051", - "name" : "locale", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "locale", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "locale", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "90531319-c512-4266-9a2c-39ca2d6c4d19", - "name" : "email", - "description" : "OpenID Connect built-in scope: email", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${emailScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "3e138a1b-7dbf-4ac2-a8b4-300a0951ccb3", - "name" : "email verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-property-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "emailVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email_verified", - "jsonType.label" : "boolean" - } - }, { - "id" : "e50753c5-bfc5-400f-87b8-be7be8c23a87", - "name" : "email", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "email", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "email", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "26fc7ca8-3561-4b0f-a79e-e983ffac13c0", - "name" : "phone", - "description" : "OpenID Connect built-in scope: phone", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${phoneScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "5afe3ea6-523f-4619-bafa-02c8516af419", - "name" : "phone number verified", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumberVerified", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number_verified", - "jsonType.label" : "boolean" - } - }, { - "id" : "7ce91fbe-4392-4126-a0df-18a6fb19c461", - "name" : "phone number", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "phoneNumber", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "phone_number", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "bfc650b4-2065-47df-807d-bd0efdb59a84", - "name" : "web-origins", - "description" : "OpenID Connect scope for add allowed web origins to the access token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "consent.screen.text" : "", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "ebafee7b-5762-45ff-8054-fad86428a70e", - "name" : "allowed web origins", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-allowed-origins-mapper", - "consentRequired" : false, - "config" : { - "access.token.claim" : "true", - "introspection.token.claim" : "true" - } - } ] - }, { - "id" : "1ef07288-a201-466e-ad80-a1160ec4d84c", - "name" : "basic", - "description" : "OpenID Connect scope for add all basic claims to the token", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "false", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "264e35b8-e5c6-4607-a412-a4b021ade86d", - "name" : "auth_time", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usersessionmodel-note-mapper", - "consentRequired" : false, - "config" : { - "user.session.note" : "AUTH_TIME", - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "auth_time", - "jsonType.label" : "long" - } - }, { - "id" : "e2d85430-984e-4933-ab8a-095018599676", - "name" : "sub", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-sub-mapper", - "consentRequired" : false, - "config" : { - "access.token.claim" : "true", - "introspection.token.claim" : "true" - } - } ] - }, { - "id" : "c2825d61-d98b-4200-9b7c-699635e4822e", - "name" : "microprofile-jwt", - "description" : "Microprofile - JWT built-in scope", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "display.on.consent.screen" : "false" - }, - "protocolMappers" : [ { - "id" : "02aef283-5cf6-4a5b-8879-0973efd8dd01", - "name" : "groups", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-realm-role-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "multivalued" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "foo", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "groups", - "jsonType.label" : "String" - } - }, { - "id" : "8938588f-e269-49c2-ad52-afe54224cdaf", - "name" : "upn", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-usermodel-attribute-mapper", - "consentRequired" : false, - "config" : { - "introspection.token.claim" : "true", - "userinfo.token.claim" : "true", - "user.attribute" : "username", - "id.token.claim" : "true", - "access.token.claim" : "true", - "claim.name" : "upn", - "jsonType.label" : "String" - } - } ] - }, { - "id" : "bfee5f0c-7b97-4569-bf06-942e6865e14c", - "name" : "role_list", - "description" : "SAML role list", - "protocol" : "saml", - "attributes" : { - "consent.screen.text" : "${samlRoleListScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "09dddeb4-9b2f-45b4-8c60-3c1d62e64d75", - "name" : "role list", - "protocol" : "saml", - "protocolMapper" : "saml-role-list-mapper", - "consentRequired" : false, - "config" : { - "single" : "false", - "attribute.nameformat" : "Basic", - "attribute.name" : "Role" - } - } ] - }, { - "id" : "773e90cb-bad8-438b-98c5-0174c799a37a", - "name" : "address", - "description" : "OpenID Connect built-in scope: address", - "protocol" : "openid-connect", - "attributes" : { - "include.in.token.scope" : "true", - "consent.screen.text" : "${addressScopeConsentText}", - "display.on.consent.screen" : "true" - }, - "protocolMappers" : [ { - "id" : "94842824-7c55-4576-ab27-e03079620f39", - "name" : "address", - "protocol" : "openid-connect", - "protocolMapper" : "oidc-address-mapper", - "consentRequired" : false, - "config" : { - "user.attribute.formatted" : "formatted", - "user.attribute.country" : "country", - "introspection.token.claim" : "true", - "user.attribute.postal_code" : "postal_code", - "userinfo.token.claim" : "true", - "user.attribute.street" : "street", - "id.token.claim" : "true", - "user.attribute.region" : "region", - "access.token.claim" : "true", - "user.attribute.locality" : "locality" - } - } ] - } ], - "defaultDefaultClientScopes" : [ "role_list", "profile", "email", "roles", "web-origins", "acr", "basic" ], - "defaultOptionalClientScopes" : [ "offline_access", "address", "phone", "microprofile-jwt" ], - "browserSecurityHeaders" : { - "contentSecurityPolicyReportOnly" : "", - "xContentTypeOptions" : "nosniff", - "referrerPolicy" : "no-referrer", - "xRobotsTag" : "none", - "xFrameOptions" : "SAMEORIGIN", - "contentSecurityPolicy" : "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", - "xXSSProtection" : "1; mode=block", - "strictTransportSecurity" : "max-age=31536000; includeSubDomains" - }, - "smtpServer" : { }, - "eventsEnabled" : false, - "eventsListeners" : [ "jboss-logging" ], - "enabledEventTypes" : [ ], - "adminEventsEnabled" : false, - "adminEventsDetailsEnabled" : false, - "identityProviders" : [ ], - "identityProviderMappers" : [ ], - "components" : { - "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy" : [ { - "id" : "87c77663-33a5-41fa-805e-ad6e16ac693a", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - }, { - "id" : "93b727d6-92be-4ae7-9436-cd8a67e13576", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-property-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "saml-user-property-mapper" ] - } - }, { - "id" : "b099d087-5954-460d-902f-def7799cb005", - "name" : "Allowed Client Scopes", - "providerId" : "allowed-client-templates", - "subType" : "authenticated", - "subComponents" : { }, - "config" : { - "allow-default-scopes" : [ "true" ] - } - }, { - "id" : "16f67ef8-1431-4241-a7a3-0849513dd422", - "name" : "Allowed Protocol Mapper Types", - "providerId" : "allowed-protocol-mappers", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper", "saml-user-property-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "oidc-usermodel-attribute-mapper", "saml-user-attribute-mapper" ] - } - }, { - "id" : "e86226d6-0944-4c08-b809-d72e6c7991c4", - "name" : "Max Clients Limit", - "providerId" : "max-clients", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "max-clients" : [ "200" ] - } - }, { - "id" : "c796a096-9c20-4983-b7bc-cb282936040f", - "name" : "Consent Required", - "providerId" : "consent-required", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - }, { - "id" : "76051f4c-b020-449d-a40f-664f918d0082", - "name" : "Trusted Hosts", - "providerId" : "trusted-hosts", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { - "host-sending-registration-request-must-match" : [ "true" ], - "client-uris-must-match" : [ "true" ] - } - }, { - "id" : "055d0fd2-9546-466d-b945-914f5ce84272", - "name" : "Full Scope Disabled", - "providerId" : "scope", - "subType" : "anonymous", - "subComponents" : { }, - "config" : { } - } ], - "org.keycloak.keys.KeyProvider" : [ { - "id" : "bb2e382d-8ed7-41ab-b545-defd7cf035dd", - "name" : "rsa-enc-generated", - "providerId" : "rsa-enc-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEowIBAAKCAQEAp+k3VByvA2qSCFXXxzJpeEUlzZiqHhWbNYy2dbd/VIBNOsaFr97fSpL1bOdEqJ20Q2xfcrI/u+Ep3CbZoHswGpfJfNU4d+yvbIFDFFavBNmtAeTxw6NiIIIvJGwnxfzed6AD7VRLDDUTLseawSesnwVWtJcF/NNiKED+CZzaXhmQBqwOrwVDlQMRUwmOhoOQWoKxnqWcvO0Gg/DFZCMT9GMwfnMRKRJE2aABix6b90aDK9AoIGtAILALOiprd2qcm5NMh9ztdSP3RpTWpvO1WqkDuxkTAL7lsc2/CymH3IQtCrQkI6f+aLZ7Xk4U9EOeq4V5HLDbHB/0fwAfyytoxwIDAQABAoIBAACllioTr0AHVsSiXzi467uH0IA+8867lLHcscPNZ3UlLNKBrjDCBzZgHEVmtNQ5Di43HEdq7mgKkzqGqKq3+aVS0cO99kpD+eMFniH0Q8VVgyv2b7dDwcyiD0VZybWD9f8wp269HWinsJDjRFEMNRkWNDK4+X4R+iZxWNnRTxYed0pdOTfWFX+iyQRefhOzReZjyxoM8xUmehiMpl4X/wYo6QxOW28ZKAtgZKDfiBymXmG1hMbk75Ktm5z5iy1vceZn19CfK56kTA4Y3OtOFwHaBu5Oq+4fgByS+EVv4dVAcR5mtKJV3z34L8xjg/J4dnosn7ZXMmCC5UWxb944Zu0CgYEA2q0zGuBaAwjjwE7AUtd+lpKJrEtm/3FzUXCSxpcgr6yGMcOaZlQKPNp+QnFOHo59O0CQqsDU7t/TRyNDKhtc4r1KYR+n8KHnW/l/bA3GIZtydFW+OcUL4lQGm1ZyVo3UsFaTCRa59TeFpDIX5gWS+/CeqgGuUdBNLlbg5D4LItsCgYEAxJHg4yhEDNpHf8oH7IlUB1NYwMX1pv0xgK9RYksCB2Z+Dec8JI9bheeb6S0pb0rkbLrwjVyNbV4/dZn6oYfLzI5gdu5/qKDxy5g+tg3C2VkLyT3ONM4IPKoCt86ZokFdzqItPPVWYZ0DUr78yzpJDjgnB84Y6HbOGx95IneT94UCgYAkIucFE/oL8lYgm2Lwzaefnkud6z/0Cn1yAdZfdu3x2eK7KoXDTzP55mli9XJhXk6Xkg3WCdOmPdqeMNeSh78LwRgfgKmx/C9NZaeG5afOOe/qBZlP1p4mIpiM5vYyE3IISeY2ZEkKmsg84AJPArDNbW/qzChQYMnAVJ8JWK9ibQKBgHBqnRpMJN7U1p8Wg4Ga6BtoZxGYJOzjUDQwD3MPQpHI474/x/2Anu7tjhTEZzXmtswX/QpbK+aoR9KRxOwsJTlPE4vwycE+ignNf8/N/ukeK8djOVKpobxP3k4QMXzBtUw/I3ABPu2ERipEX346Tx16r5efHk+T4jtQvI4hpNWhAoGBAK493H9KZQdjlMcoMvHS2wH26vLRIOA1/urC5KKMSHqcTdFneXFdz55u78uuCCRC9Qmv89RvWyfp7asadymtaFYKQGZnGUvNpNOm+c/nPy2bywrCHcUWU8OYUyrJM3m2AQuSwQVyKjWCJrAtclomd2fLvpJ1KF26l0fq1BNF23DS" ], - "certificate" : [ "MIICpTCCAY0CBgGRI/HPRTANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtkZXZlbG9wbWVudDAeFw0yNDA4MDUxOTA3MTVaFw0zNDA4MDUxOTA4NTVaMBYxFDASBgNVBAMMC2RldmVsb3BtZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp+k3VByvA2qSCFXXxzJpeEUlzZiqHhWbNYy2dbd/VIBNOsaFr97fSpL1bOdEqJ20Q2xfcrI/u+Ep3CbZoHswGpfJfNU4d+yvbIFDFFavBNmtAeTxw6NiIIIvJGwnxfzed6AD7VRLDDUTLseawSesnwVWtJcF/NNiKED+CZzaXhmQBqwOrwVDlQMRUwmOhoOQWoKxnqWcvO0Gg/DFZCMT9GMwfnMRKRJE2aABix6b90aDK9AoIGtAILALOiprd2qcm5NMh9ztdSP3RpTWpvO1WqkDuxkTAL7lsc2/CymH3IQtCrQkI6f+aLZ7Xk4U9EOeq4V5HLDbHB/0fwAfyytoxwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCUtjMYZWBiildDYkd2iI8b8G1n6qvZ1q+x/JIYUNVbFWUfC7lfiCIua6YwgJluI4RcZ5hFpHWWxzW/Cz8RTymsM7oCwGa3Nv5cUZLGOB4B6FC3nDNdJm1WP2ICqsu9HpS6BReS8WCTQ+r+zgE/hO9SCtffuEAmib+ICKOE7KhYkqveN835la9rlfVbgbKl7aHdtJz+JqFEdgAM+kdpuT+csINuoXQpn5MWBs1wvdQNu7tA7m01vCaJg00a43FxSXJjq/lxUGXLdRoAqe0SFqkMnpdqfV8XZClA9Kgs6FBFG6xKikJLeD5M61UA/P8WXHEgPbWcy1chFYQyeimmxEzl" ], - "priority" : [ "100" ], - "algorithm" : [ "RSA-OAEP" ] - } - }, { - "id" : "ca7e3c60-4007-4fbd-bb58-80989d4ef95f", - "name" : "aes-generated", - "providerId" : "aes-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "40b90151-b1ef-4e82-a179-7dbc45ed2db3" ], - "secret" : [ "o1R5W3Ez-r_9PK6xtUJfFg" ], - "priority" : [ "100" ] - } - }, { - "id" : "5ddf1462-4890-492a-919d-5a4cf34d8e74", - "name" : "rsa-generated", - "providerId" : "rsa-generated", - "subComponents" : { }, - "config" : { - "privateKey" : [ "MIIEowIBAAKCAQEAuPtq+FpY5j7YFdxxNRcpNooCMqYcU7h2s2DZs3fpBURAR3WTQ/kq9KuNnlub8GSGuoKKZGL6dE6cQmfGyB+7OAHuwc19R/mVfTFC8SFLQHbdPfPtZJai2PR0OuW7zM6XY/lItaUN0+b8qSzQ1ymSakC6vY8//A+ZGpta55vveJCdx3gTI7BowvOE1HBRM/2cl1I6MwQ9Wb7AJHIcoaDUcv5PX7iW3gP7CcMbyLR6fkIvPZ85IldSjJu/k4uSKH5Xpv0sDen9YJjvWubjnisAoLVjNmky78KWcZ7xxPXIHRuZv6OsHl2KrkQEOJHL2EU2S7GlgNFCGMfo9wQrvPwyiQIDAQABAoIBAFqdjsEqQPJJRsEuw6YmOmIlKP52sNmyOzOT9hEcZGHYFUTsK7/Yh1pjz3QLnFAeboFfATw74ESpXZKKE65lHOlKyRLW/tALS1eQkiJdFOf3UlnO7DOiNxPgbC+N6nlhmne23g9dU3DCbnLRKVy9WKQEIXZKfiWT3oRSJi3fDWgZWc3Ds72D/T4Oy8go1zMbHIe5kpzhScoL72YwaefsqwNFtY0sqts0uQF2mVXXeFfq2lvgcrob9sfiDx+NW5m3In8Qa0+VL/oeYvgbUPi3oi2LxH6xDHbQUKq4DUKBlPMuaSW18E1xXV6qUu4DxMA8+eqo4KLAVDyudSmPQj3gsCECgYEA68Seb4NUvfGRfSMGouEfAJfYf2MoEW5VdIbgVoVYlJ9mI3zjdLsHsfmXmrMnvQWBx9YGL+XRjcpTsPzH0DAD2+JjFzIukv7l6osG67JA3i5GHYXuFL3Ml0MHCWvbSgrHdDAm2/HroifZEbPBjFsq8Kb1wFQ0AR7ClMdgxrDMJOcCgYEAyNsgkIrtB11gKlc8pf/LByWjfsueXfhhHk5G0RSCyZ6DXlK1C55te9tJ+Y2Z5VjUaqsRbIqNKVRL1CRZQzjSzYAxYPaFYvy6gXmTukQB5/i8D/TSjXNfxOpEBTnKX4psas51TS4ncJg35oXoh+zahqawczYs7Ein254n3J4vjw8CgYBn1Jlpxs7FL+PA1nIPvVDn8/d2cnas5ohf3x5hPCx8l45lxpRtTgjfimoHySqRBiHXnhvvcCjPZvFgmpJszxiD97ln98OnHPaoSj3sAv6qWnqqedcV71hwrSYmMgfLHeAk/Se/6VS6fw4Ly7xLUcMhZIYKA4s7iw5qczvdhPMCtQKBgQCbLCc5ZAsA4JO1wlW5jSeGKv7nq1l7EbO+HQ065BSyvQKSsWTrSAcfY+f/ovTdKcZZbjX03Al4f4Zhq39GnrTFTJ9ZYLrmIYfZFVsa0QWD+DcaQLMV0qePUskgHGiLbT6bOUuIR/ryUrcFIjjmIgcldcvmBlmoZe1AjywOlir54wKBgAuPjALJsstfSbI7ER+acyTNTTlVxVn9CqKWv5UTeLNpIBBjSi36jYVAGmpCY2rgHVZE5EZCejiCn18mdMVQS3+G5Dmb9vAldoxaRim5oMSbnyJt3btszf29iM2nFzw/aGKzHVKDWI473SA8krDtvvHsBEDEEKHNPlF4TE+aowUV" ], - "certificate" : [ "MIICpTCCAY0CBgGRI/HPwjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtkZXZlbG9wbWVudDAeFw0yNDA4MDUxOTA3MTVaFw0zNDA4MDUxOTA4NTVaMBYxFDASBgNVBAMMC2RldmVsb3BtZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuPtq+FpY5j7YFdxxNRcpNooCMqYcU7h2s2DZs3fpBURAR3WTQ/kq9KuNnlub8GSGuoKKZGL6dE6cQmfGyB+7OAHuwc19R/mVfTFC8SFLQHbdPfPtZJai2PR0OuW7zM6XY/lItaUN0+b8qSzQ1ymSakC6vY8//A+ZGpta55vveJCdx3gTI7BowvOE1HBRM/2cl1I6MwQ9Wb7AJHIcoaDUcv5PX7iW3gP7CcMbyLR6fkIvPZ85IldSjJu/k4uSKH5Xpv0sDen9YJjvWubjnisAoLVjNmky78KWcZ7xxPXIHRuZv6OsHl2KrkQEOJHL2EU2S7GlgNFCGMfo9wQrvPwyiQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBqMz0QkjIrMnmF3VrYqdfHsgZptyI6jj1GQ0MR4qJu4Dl5ojhGXFukbAu8xBjnVhv1uc6IWjfWT7YJbUc6n752uQH3//bK46K/vXhbGMP+lCpyc5CT69FOJtutT+/A7R6/8WS6M91BwYF9pX3XHQ1109ylAzhW+qV1LqdJMipy4FB7yYDImLsf6Z8JtmSveopwMQ8A/GJTG5LEPnoYqJ8BY85yG/GszgXcn63f6iTT/7BceKOu7kp0l+Np6qooMddniV4917P+aCU/T7dkz+afj0JURf1fnsITXu02iyi+aHmHihOuYzqobeXDJU0FqGg0qQGJU9QLye5jbZqFM2yf" ], - "priority" : [ "100" ] - } - }, { - "id" : "64782b75-6f52-4f1d-af06-09ad6bfcdfb9", - "name" : "hmac-generated-hs512", - "providerId" : "hmac-generated", - "subComponents" : { }, - "config" : { - "kid" : [ "1c986f4a-6e05-43e1-97a8-d756aed963ff" ], - "secret" : [ "EWGJDe4oUzHqg-arMOUMeEIqb1k0l8O_RpYsmhHl0oiTDqVFnlHB3cBAyhPQYdEkkeFoVnjBA5t46zxj4sOLAMZ9McOgifAe_WBe4_RrxQDhMFMLMNyWnCnFP_jDAFj98P0xyv8atwZCG5xPRFDrO9pJKxO472kG6ws0CZ-CpdY" ], - "priority" : [ "100" ], - "algorithm" : [ "HS512" ] - } - } ] - }, - "internationalizationEnabled" : false, - "supportedLocales" : [ ], - "authenticationFlows" : [ { - "id" : "17180a8f-635e-42e3-9870-88b7b61fb915", - "alias" : "Account verification options", - "description" : "Method with which to verity the existing account", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "idp-email-verification", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Verify Existing Account by Re-authentication", - "userSetupAllowed" : false - } ] - }, { - "id" : "c416b7a7-45c8-4f68-99b9-0fd7d817fe18", - "alias" : "Browser - Conditional OTP", - "description" : "Flow to determine if the OTP is required for the authentication", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-otp-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "7d9a6e6e-6a2e-4453-aac4-fd2e0f158f4b", - "alias" : "Direct Grant - Conditional OTP", - "description" : "Flow to determine if the OTP is required for the authentication", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "direct-grant-validate-otp", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "5a9053a6-432c-4380-b267-96f86cfac2a7", - "alias" : "First broker login - Conditional OTP", - "description" : "Flow to determine if the OTP is required for the authentication", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-otp-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "f12fa4ea-704b-4f25-8a50-2b6c011d0249", - "alias" : "Handle Existing Account", - "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "idp-confirm-link", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Account verification options", - "userSetupAllowed" : false - } ] - }, { - "id" : "1fe6bf4e-f2f8-4631-b024-6e8a5bd8c42e", - "alias" : "Reset - Conditional OTP", - "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "conditional-user-configured", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-otp", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "6246dbfb-a619-4635-ab3e-b394f0ed4af3", - "alias" : "User creation or linking", - "description" : "Flow for the existing/non-existing user alternatives", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticatorConfig" : "create unique user config", - "authenticator" : "idp-create-user-if-unique", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Handle Existing Account", - "userSetupAllowed" : false - } ] - }, { - "id" : "efa63a87-ea5c-4519-a323-11099438f81e", - "alias" : "Verify Existing Account by Re-authentication", - "description" : "Reauthentication of existing account", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "idp-username-password-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "First broker login - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "2cd74a88-7aff-42ad-a94e-776089c0aaca", - "alias" : "browser", - "description" : "browser based authentication", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "auth-cookie", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "auth-spnego", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "identity-provider-redirector", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 25, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "ALTERNATIVE", - "priority" : 30, - "autheticatorFlow" : true, - "flowAlias" : "forms", - "userSetupAllowed" : false - } ] - }, { - "id" : "f14b6e2e-bf4f-4dc3-906e-8e417cb8a96b", - "alias" : "clients", - "description" : "Base authentication for clients", - "providerId" : "client-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "client-secret", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-jwt", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-secret-jwt", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 30, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "client-x509", - "authenticatorFlow" : false, - "requirement" : "ALTERNATIVE", - "priority" : 40, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "704eac4a-0f3f-42b1-8326-ed8193f7c5e0", - "alias" : "direct grant", - "description" : "OpenID Connect Resource Owner Grant", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "direct-grant-validate-username", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "direct-grant-validate-password", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 30, - "autheticatorFlow" : true, - "flowAlias" : "Direct Grant - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "ea358460-4bdb-4926-8bdb-9ae28b76a73a", - "alias" : "docker auth", - "description" : "Used by Docker clients to authenticate against the IDP", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "docker-http-basic-authenticator", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "064b83a9-eff4-47c0-a885-5a2552a390b7", - "alias" : "first broker login", - "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticatorConfig" : "review profile config", - "authenticator" : "idp-review-profile", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "User creation or linking", - "userSetupAllowed" : false - } ] - }, { - "id" : "224e3374-9c81-437a-9abb-557eba90dcea", - "alias" : "forms", - "description" : "Username, password, otp and other auth forms.", - "providerId" : "basic-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "auth-username-password-form", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 20, - "autheticatorFlow" : true, - "flowAlias" : "Browser - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "ccfe54ac-0522-45a5-8cf5-a3bd89396ea8", - "alias" : "registration", - "description" : "registration flow", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-page-form", - "authenticatorFlow" : true, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : true, - "flowAlias" : "registration form", - "userSetupAllowed" : false - } ] - }, { - "id" : "09319bf1-dc33-4d5a-9866-20ef01e3364e", - "alias" : "registration form", - "description" : "registration form", - "providerId" : "form-flow", - "topLevel" : false, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "registration-user-creation", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-password-action", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 50, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-recaptcha-action", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 60, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "registration-terms-and-conditions", - "authenticatorFlow" : false, - "requirement" : "DISABLED", - "priority" : 70, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - }, { - "id" : "e55eed94-e227-4edc-af34-5d8595fb2379", - "alias" : "reset credentials", - "description" : "Reset credentials for a user if they forgot their password or something", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "reset-credentials-choose-user", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-credential-email", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 20, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticator" : "reset-password", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 30, - "autheticatorFlow" : false, - "userSetupAllowed" : false - }, { - "authenticatorFlow" : true, - "requirement" : "CONDITIONAL", - "priority" : 40, - "autheticatorFlow" : true, - "flowAlias" : "Reset - Conditional OTP", - "userSetupAllowed" : false - } ] - }, { - "id" : "4ce089cb-31f2-4c4c-99a8-fd7f2c6b458e", - "alias" : "saml ecp", - "description" : "SAML ECP Profile Authentication Flow", - "providerId" : "basic-flow", - "topLevel" : true, - "builtIn" : true, - "authenticationExecutions" : [ { - "authenticator" : "http-basic-authenticator", - "authenticatorFlow" : false, - "requirement" : "REQUIRED", - "priority" : 10, - "autheticatorFlow" : false, - "userSetupAllowed" : false - } ] - } ], - "authenticatorConfig" : [ { - "id" : "ca32fd2e-b807-49d6-aa2a-ece90de7c6d1", - "alias" : "create unique user config", - "config" : { - "require.password.update.after.registration" : "false" - } - }, { - "id" : "87fbbfb1-9fad-4954-85be-84d15d6f8bc6", - "alias" : "review profile config", - "config" : { - "update.profile.on.first.login" : "missing" - } - } ], - "requiredActions" : [ { - "alias" : "CONFIGURE_TOTP", - "name" : "Configure OTP", - "providerId" : "CONFIGURE_TOTP", - "enabled" : true, - "defaultAction" : false, - "priority" : 10, - "config" : { } - }, { - "alias" : "TERMS_AND_CONDITIONS", - "name" : "Terms and Conditions", - "providerId" : "TERMS_AND_CONDITIONS", - "enabled" : false, - "defaultAction" : false, - "priority" : 20, - "config" : { } - }, { - "alias" : "UPDATE_PASSWORD", - "name" : "Update Password", - "providerId" : "UPDATE_PASSWORD", - "enabled" : true, - "defaultAction" : false, - "priority" : 30, - "config" : { } - }, { - "alias" : "UPDATE_PROFILE", - "name" : "Update Profile", - "providerId" : "UPDATE_PROFILE", - "enabled" : true, - "defaultAction" : false, - "priority" : 40, - "config" : { } - }, { - "alias" : "VERIFY_EMAIL", - "name" : "Verify Email", - "providerId" : "VERIFY_EMAIL", - "enabled" : true, - "defaultAction" : false, - "priority" : 50, - "config" : { } - }, { - "alias" : "delete_account", - "name" : "Delete Account", - "providerId" : "delete_account", - "enabled" : false, - "defaultAction" : false, - "priority" : 60, - "config" : { } - }, { - "alias" : "webauthn-register", - "name" : "Webauthn Register", - "providerId" : "webauthn-register", - "enabled" : true, - "defaultAction" : false, - "priority" : 70, - "config" : { } - }, { - "alias" : "webauthn-register-passwordless", - "name" : "Webauthn Register Passwordless", - "providerId" : "webauthn-register-passwordless", - "enabled" : true, - "defaultAction" : false, - "priority" : 80, - "config" : { } - }, { - "alias" : "VERIFY_PROFILE", - "name" : "Verify Profile", - "providerId" : "VERIFY_PROFILE", - "enabled" : true, - "defaultAction" : false, - "priority" : 90, - "config" : { } - }, { - "alias" : "delete_credential", - "name" : "Delete Credential", - "providerId" : "delete_credential", - "enabled" : true, - "defaultAction" : false, - "priority" : 100, - "config" : { } - }, { - "alias" : "update_user_locale", - "name" : "Update User Locale", - "providerId" : "update_user_locale", - "enabled" : true, - "defaultAction" : false, - "priority" : 1000, - "config" : { } - } ], - "browserFlow" : "browser", - "registrationFlow" : "registration", - "directGrantFlow" : "direct grant", - "resetCredentialsFlow" : "reset credentials", - "clientAuthenticationFlow" : "clients", - "dockerAuthenticationFlow" : "docker auth", - "firstBrokerLoginFlow" : "first broker login", - "attributes" : { - "cibaBackchannelTokenDeliveryMode" : "poll", - "cibaAuthRequestedUserHint" : "login_hint", - "clientOfflineSessionMaxLifespan" : "0", - "oauth2DevicePollingInterval" : "5", - "clientSessionIdleTimeout" : "0", - "clientOfflineSessionIdleTimeout" : "0", - "cibaInterval" : "5", - "realmReusableOtpCode" : "false", - "cibaExpiresIn" : "120", - "oauth2DeviceCodeLifespan" : "600", - "parRequestUriLifespan" : "60", - "clientSessionMaxLifespan" : "0", - "organizationsEnabled" : "false" - }, - "keycloakVersion" : "25.0.2", - "userManagedAccessAllowed" : false, - "organizationsEnabled" : false, - "clientProfiles" : { - "profiles" : [ ] - }, - "clientPolicies" : { - "policies" : [ ] - } -} \ No newline at end of file diff --git a/vegasco-server.slnx b/vegasco-server.slnx index 3aaf43d..ce8d932 100644 --- a/vegasco-server.slnx +++ b/vegasco-server.slnx @@ -10,7 +10,6 @@ - -- 2.49.1 From bbac9536601beb2cc3ef3477fc42e7a5bb35f475 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Dec 2024 15:59:31 +0100 Subject: [PATCH 049/150] Update legacy sln file --- vegasco-server.sln | 63 +++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/vegasco-server.sln b/vegasco-server.sln index 87881e8..2e7f0b3 100644 --- a/vegasco-server.sln +++ b/vegasco-server.sln @@ -1,27 +1,24 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# 17 -VisualStudioVersion = 17.0.31903.59 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35617.110 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi", "src\WebApi\WebApi.csproj", "{9FF3C98A-5085-4EBE-A980-DB2148B0C00A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C051A684-BD6A-43F2-B0CC-F3C2315D99E3}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A16251C2-47DB-4017-812B-CA18B280E049}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{089100B1-113F-4E66-888A-E83F3999EAFD}" ProjectSection(SolutionItems) = preProject - README.md = README.md .drone.yml = .drone.yml - version.json = version.json Dockerfile = Dockerfile + README.md = README.md + version.json = version.json EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{437DE053-1DAB-4EEF-BEA6-E3B5179692F8}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests.Unit", "tests\WebApi.Tests.Unit\WebApi.Tests.Unit.csproj", "{5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi", "src\WebApi\WebApi.csproj", "{1B0A04C3-E6BC-0FB7-7994-7C99BDAB1788}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Tests.Integration", "tests\WebApi.Tests.Integration\WebApi.Tests.Integration.csproj", "{0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.System", "tests\WebApi.Tests.System\WebApi.Tests.System.csproj", "{21418359-4A20-4F4A-B26C-A75A2B70AA10}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.Integration", "tests\WebApi.Tests.Integration\WebApi.Tests.Integration.csproj", "{72BF8CBC-E916-1472-A1E2-8F5DCF1A95C6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.Unit", "tests\WebApi.Tests.Unit\WebApi.Tests.Unit.csproj", "{2DD4D427-6FA5-EC56-76FC-9D71C4631E00}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -29,33 +26,25 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9FF3C98A-5085-4EBE-A980-DB2148B0C00A}.Release|Any CPU.Build.0 = Release|Any CPU - {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E}.Release|Any CPU.Build.0 = Release|Any CPU - {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326}.Release|Any CPU.Build.0 = Release|Any CPU - {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21418359-4A20-4F4A-B26C-A75A2B70AA10}.Release|Any CPU.Build.0 = Release|Any CPU + {1B0A04C3-E6BC-0FB7-7994-7C99BDAB1788}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B0A04C3-E6BC-0FB7-7994-7C99BDAB1788}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B0A04C3-E6BC-0FB7-7994-7C99BDAB1788}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B0A04C3-E6BC-0FB7-7994-7C99BDAB1788}.Release|Any CPU.Build.0 = Release|Any CPU + {72BF8CBC-E916-1472-A1E2-8F5DCF1A95C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72BF8CBC-E916-1472-A1E2-8F5DCF1A95C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72BF8CBC-E916-1472-A1E2-8F5DCF1A95C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72BF8CBC-E916-1472-A1E2-8F5DCF1A95C6}.Release|Any CPU.Build.0 = Release|Any CPU + {2DD4D427-6FA5-EC56-76FC-9D71C4631E00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DD4D427-6FA5-EC56-76FC-9D71C4631E00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DD4D427-6FA5-EC56-76FC-9D71C4631E00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DD4D427-6FA5-EC56-76FC-9D71C4631E00}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {9FF3C98A-5085-4EBE-A980-DB2148B0C00A} = {C051A684-BD6A-43F2-B0CC-F3C2315D99E3} - {5BA94D65-1D04-49EA-B7CC-F3719DE2D97E} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} - {0B1F3D81-95E8-4CFC-8A90-8A3CB2549326} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} - {21418359-4A20-4F4A-B26C-A75A2B70AA10} = {437DE053-1DAB-4EEF-BEA6-E3B5179692F8} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {7813E32D-AE19-479C-853B-063882D2D05A} + {1B0A04C3-E6BC-0FB7-7994-7C99BDAB1788} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {72BF8CBC-E916-1472-A1E2-8F5DCF1A95C6} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {2DD4D427-6FA5-EC56-76FC-9D71C4631E00} = {0AB3BF05-4346-4AA6-1389-037BE0695223} EndGlobalSection EndGlobal -- 2.49.1 From 6d23494fd368ee4132743da838e2d620d995cfd3 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Dec 2024 17:01:18 +0100 Subject: [PATCH 050/150] Add Aspire orchestration Therefore remove previous OpenTelemetry configuration and use the one provided in service defaults --- .../Constants.cs | 16 +++ .../Vegasco.Server.AppHost.Shared.csproj | 9 ++ src/Vegasco.Server.AppHost/Program.cs | 14 +++ .../Properties/launchSettings.json | 29 +++++ .../Vegasco.Server.AppHost.csproj | 24 ++++ .../appsettings.Development.json | 8 ++ src/Vegasco.Server.AppHost/appsettings.json | 9 ++ .../Extensions.cs | 119 ++++++++++++++++++ .../Vegasco.Server.ServiceDefaults.csproj | 22 ++++ src/WebApi/Common/Constants.cs | 2 - .../Common/DependencyInjectionExtensions.cs | 46 ++----- src/WebApi/Common/StartupExtensions.cs | 4 +- src/WebApi/WebApi.csproj | 6 + src/WebApi/appsettings.Development.json | 3 - vegasco-server.slnx | 3 + 15 files changed, 270 insertions(+), 44 deletions(-) create mode 100644 src/Vegasco.Server.AppHost.Shared/Constants.cs create mode 100644 src/Vegasco.Server.AppHost.Shared/Vegasco.Server.AppHost.Shared.csproj create mode 100644 src/Vegasco.Server.AppHost/Program.cs create mode 100644 src/Vegasco.Server.AppHost/Properties/launchSettings.json create mode 100644 src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj create mode 100644 src/Vegasco.Server.AppHost/appsettings.Development.json create mode 100644 src/Vegasco.Server.AppHost/appsettings.json create mode 100644 src/Vegasco.Server.ServiceDefaults/Extensions.cs create mode 100644 src/Vegasco.Server.ServiceDefaults/Vegasco.Server.ServiceDefaults.csproj diff --git a/src/Vegasco.Server.AppHost.Shared/Constants.cs b/src/Vegasco.Server.AppHost.Shared/Constants.cs new file mode 100644 index 0000000..d9a0f57 --- /dev/null +++ b/src/Vegasco.Server.AppHost.Shared/Constants.cs @@ -0,0 +1,16 @@ +namespace Vegasco.Server.AppHost.Shared; + +public static class Constants +{ + public static class Projects + { + public const string WebApiName = "webapi"; + } + + public static class Database + { + public const string ServiceName = "postgres"; + + public const string Name = "vegasco"; + } +} diff --git a/src/Vegasco.Server.AppHost.Shared/Vegasco.Server.AppHost.Shared.csproj b/src/Vegasco.Server.AppHost.Shared/Vegasco.Server.AppHost.Shared.csproj new file mode 100644 index 0000000..125f4c9 --- /dev/null +++ b/src/Vegasco.Server.AppHost.Shared/Vegasco.Server.AppHost.Shared.csproj @@ -0,0 +1,9 @@ + + + + net9.0 + enable + enable + + + diff --git a/src/Vegasco.Server.AppHost/Program.cs b/src/Vegasco.Server.AppHost/Program.cs new file mode 100644 index 0000000..bda0036 --- /dev/null +++ b/src/Vegasco.Server.AppHost/Program.cs @@ -0,0 +1,14 @@ +using Vegasco.Server.AppHost.Shared; + +var builder = DistributedApplication.CreateBuilder(args); + +var postgres = builder.AddPostgres(Constants.Database.ServiceName) + .WithDataVolume() + .AddDatabase(Constants.Database.Name); + +builder + .AddProject(Constants.Projects.WebApiName) + .WithReference(postgres) + .WaitFor(postgres); + +builder.Build().Run(); diff --git a/src/Vegasco.Server.AppHost/Properties/launchSettings.json b/src/Vegasco.Server.AppHost/Properties/launchSettings.json new file mode 100644 index 0000000..5a2b722 --- /dev/null +++ b/src/Vegasco.Server.AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17055;http://localhost:15102", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21122", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22235" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15102", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19222", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20257" + } + } + } +} diff --git a/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj b/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj new file mode 100644 index 0000000..c22dc1c --- /dev/null +++ b/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj @@ -0,0 +1,24 @@ + + + + + + Exe + net9.0 + enable + enable + true + bb714834-9872-4af6-b154-0b98b14fcca2 + + + + + + + + + + + + + diff --git a/src/Vegasco.Server.AppHost/appsettings.Development.json b/src/Vegasco.Server.AppHost/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/Vegasco.Server.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Vegasco.Server.AppHost/appsettings.json b/src/Vegasco.Server.AppHost/appsettings.json new file mode 100644 index 0000000..31c092a --- /dev/null +++ b/src/Vegasco.Server.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/src/Vegasco.Server.ServiceDefaults/Extensions.cs b/src/Vegasco.Server.ServiceDefaults/Extensions.cs new file mode 100644 index 0000000..13151bf --- /dev/null +++ b/src/Vegasco.Server.ServiceDefaults/Extensions.cs @@ -0,0 +1,119 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ServiceDiscovery; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation() + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} diff --git a/src/Vegasco.Server.ServiceDefaults/Vegasco.Server.ServiceDefaults.csproj b/src/Vegasco.Server.ServiceDefaults/Vegasco.Server.ServiceDefaults.csproj new file mode 100644 index 0000000..24b1b4f --- /dev/null +++ b/src/Vegasco.Server.ServiceDefaults/Vegasco.Server.ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net9.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/src/WebApi/Common/Constants.cs b/src/WebApi/Common/Constants.cs index bc15221..c160ccc 100644 --- a/src/WebApi/Common/Constants.cs +++ b/src/WebApi/Common/Constants.cs @@ -2,8 +2,6 @@ public static class Constants { - public const string AppOtelName = "Vegasco.Api"; - public static class Authorization { public const string RequireAuthenticatedUserPolicy = "RequireAuthenticatedUser"; diff --git a/src/WebApi/Common/DependencyInjectionExtensions.cs b/src/WebApi/Common/DependencyInjectionExtensions.cs index 03a9261..7c7e8a4 100644 --- a/src/WebApi/Common/DependencyInjectionExtensions.cs +++ b/src/WebApi/Common/DependencyInjectionExtensions.cs @@ -1,10 +1,7 @@ using Asp.Versioning; using FluentValidation; using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; -using OpenTelemetry.Trace; -using System.Diagnostics; using Vegasco.WebApi.Authentication; using Vegasco.WebApi.Endpoints; using Vegasco.WebApi.Endpoints.OpenApi; @@ -20,15 +17,15 @@ public static class DependencyInjectionExtensions /// /// /// - public static void AddWebApiServices(this IServiceCollection services, IConfiguration configuration, IHostEnvironment environment) + public static void AddWebApiServices(this IHostApplicationBuilder builder) { - services + builder.Services .AddMiscellaneousServices() .AddOpenApi() .AddApiVersioning() - .AddOtel() - .AddAuthenticationAndAuthorization(environment) - .AddDbContext(configuration); + .AddAuthenticationAndAuthorization(builder.Environment); + + builder.AddDbContext(); } private static IServiceCollection AddMiscellaneousServices(this IServiceCollection services) @@ -98,26 +95,6 @@ public static class DependencyInjectionExtensions return services; } - private static IServiceCollection AddOtel(this IServiceCollection services) - { - Activity.DefaultIdFormat = ActivityIdFormat.W3C; - - ActivitySource activitySource = new(Constants.AppOtelName); - services.AddSingleton(activitySource); - - services.AddOpenTelemetry() - .WithTracing(t => - { - t.AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddOtlpExporter() - .AddSource(activitySource.Name); - }) - .WithMetrics(); - - return services; - } - private static IServiceCollection AddAuthenticationAndAuthorization(this IServiceCollection services, IHostEnvironment environment) { services.AddOptions() @@ -153,16 +130,9 @@ public static class DependencyInjectionExtensions return services; } - private static IServiceCollection AddDbContext(this IServiceCollection services, IConfiguration configuration) + private static IHostApplicationBuilder AddDbContext(this IHostApplicationBuilder builder) { - services.AddDbContext(o => - { - o.UseNpgsql(configuration.GetConnectionString("Database"), c => - { - c.EnableRetryOnFailure(); - }); - }); - - return services; + builder.AddNpgsqlDbContext(Server.AppHost.Shared.Constants.Database.Name); + return builder; } } diff --git a/src/WebApi/Common/StartupExtensions.cs b/src/WebApi/Common/StartupExtensions.cs index 351875f..6b2a7fd 100644 --- a/src/WebApi/Common/StartupExtensions.cs +++ b/src/WebApi/Common/StartupExtensions.cs @@ -9,9 +9,11 @@ internal static class StartupExtensions { internal static WebApplication ConfigureServices(this WebApplicationBuilder builder) { + builder.AddServiceDefaults(); + builder.Configuration.AddEnvironmentVariables("Vegasco_"); - builder.Services.AddWebApiServices(builder.Configuration, builder.Environment); + builder.AddWebApiServices(); WebApplication app = builder.Build(); return app; diff --git a/src/WebApi/WebApi.csproj b/src/WebApi/WebApi.csproj index 4a838d9..8f2416f 100644 --- a/src/WebApi/WebApi.csproj +++ b/src/WebApi/WebApi.csproj @@ -13,6 +13,7 @@ + @@ -33,6 +34,11 @@ + + + + + diff --git a/src/WebApi/appsettings.Development.json b/src/WebApi/appsettings.Development.json index e66cc17..0c208ae 100644 --- a/src/WebApi/appsettings.Development.json +++ b/src/WebApi/appsettings.Development.json @@ -1,7 +1,4 @@ { - "ConnectionStrings": { - "Database": "Host=localhost;Port=5432;Database=postgres;Username=postgres;Password=postgres" - }, "Logging": { "LogLevel": { "Default": "Information", diff --git a/vegasco-server.slnx b/vegasco-server.slnx index ce8d932..5cc0fb0 100644 --- a/vegasco-server.slnx +++ b/vegasco-server.slnx @@ -6,6 +6,9 @@ + + + -- 2.49.1 From ff2707a0e837a6bdd20b8cff0f67c150c0e80161 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Dec 2024 17:08:01 +0100 Subject: [PATCH 051/150] Add Aspire documentation to README --- README.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1c1b0e7..aed75ba 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ # Vegasco Server -Backend for the vegasco (***VE***hicle ***GAS*** ***CO***nsumption) application. +Backend for the vegasco (**VE**hicle **GAS** **CO**nsumption) application. ## Getting Started ### Configuration | Configuration | Description | Default | Required | -|--------------------------|-------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|----------| -| JWT:MetadataUrl | The oidc meta data url | - | true | -| JWT:ValidAudience | The valid audience of the JWT token. | - | true | -| JWT:NameClaimType | The claim type of the user's name claim. | http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name | false | -| JWT:AllowHttpMetadataUrl | Whether to allow the meta data url to have http as protocol. Always true when `ASPNETCORE_ENVIRONMENT=true` | false | false | +|--------------------------|---------------------------------------------------------------------------------------------------------------|------------------------------------------------------------|----------| +| JWT:MetadataUrl | The oidc meta data url | - | true | +| JWT:ValidAudience | The valid audience of the JWT token. | - | true | +| JWT:NameClaimType | The claim type of the user's name claim. For keycloak, using `preferred_username` is often the better choice. | http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name | false | +| JWT:AllowHttpMetadataUrl | Whether to allow the meta data url to have http as protocol. Always true when `ASPNETCORE_ENVIRONMENT=true` | false | false | The application uses the prefix `Vegasco_` for environment variable names. The prefix is removed when the application reads the environment variables and duplicate entries are overwritten by the environment variables. @@ -56,3 +56,12 @@ As appsettings.json (or a environment specific appsettings.*.json): } } ``` + +### Running the application + +The solution uses Aspire to orchestrate the application. Specifically, it introduces sensible service defaults, including but not limited to OpenTelemetry, +creates a Postgres database as a docker container, and starts the WebApi with the correct configuration to communicate with the database. + +Ensure you have an identity provider set up, for example Keycloak, and configured the relevant options described above. + +Then, to run the application, ensure you have Docker running, then run the `Vegasco.Server.AppHost` launch profile. -- 2.49.1 From 4ea0978cf67dd6e0e882e3e3b05e1de8a5f95c7e Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Dec 2024 17:10:36 +0100 Subject: [PATCH 052/150] Fix integration test database connection string --- .../WebApi.Tests.Integration/WebApi.Tests.Integration.csproj | 1 + tests/WebApi.Tests.Integration/WebAppFactory.cs | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj index 8b543ef..4d73780 100644 --- a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj +++ b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj @@ -31,6 +31,7 @@ + diff --git a/tests/WebApi.Tests.Integration/WebAppFactory.cs b/tests/WebApi.Tests.Integration/WebAppFactory.cs index cd057b4..7c9f74d 100644 --- a/tests/WebApi.Tests.Integration/WebAppFactory.cs +++ b/tests/WebApi.Tests.Integration/WebAppFactory.cs @@ -3,13 +3,11 @@ using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Testcontainers.PostgreSql; using Vegasco.WebApi.Common; -using Vegasco.WebApi.Persistence; namespace WebApi.Tests.Integration; @@ -40,7 +38,7 @@ public sealed class WebAppFactory : WebApplicationFactory, IAsync { IEnumerable> customConfig = [ - new KeyValuePair("ConnectionStrings:Database", _database.GetConnectionString()), + new KeyValuePair($"ConnectionStrings:{Vegasco.Server.AppHost.Shared.Constants.Database.Name}", _database.GetConnectionString()), new KeyValuePair("JWT:ValidAudience", "https://localhost"), new KeyValuePair("JWT:MetadataUrl", "https://localhost"), new KeyValuePair("JWT:NameClaimType", null), -- 2.49.1 From cb440e7c6d9bec0fbc8c8572eb361dcc67fba371 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Dec 2024 17:11:09 +0100 Subject: [PATCH 053/150] Use latest postgres version in integration tests --- tests/WebApi.Tests.Integration/WebAppFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/WebApi.Tests.Integration/WebAppFactory.cs b/tests/WebApi.Tests.Integration/WebAppFactory.cs index 7c9f74d..892518a 100644 --- a/tests/WebApi.Tests.Integration/WebAppFactory.cs +++ b/tests/WebApi.Tests.Integration/WebAppFactory.cs @@ -18,7 +18,7 @@ public sealed class WebAppFactory : WebApplicationFactory, IAsync .WithImagePullPolicy(PullPolicy.Always) .Build(); - private const string DockerImage = "postgres:16.3-alpine"; + private const string DockerImage = "postgres:alpine"; public HttpClient HttpClient => CreateClient(); -- 2.49.1 From 05686c4cddb8c3b02083184c17756345f3b4fa2e Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Dec 2024 17:20:10 +0100 Subject: [PATCH 054/150] Add own test step --- .drone.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.drone.yml b/.drone.yml index 5f155cd..d505daf 100644 --- a/.drone.yml +++ b/.drone.yml @@ -33,6 +33,19 @@ steps: path: /var/run depends_on: - compile + + - name: test2 + image: mcr.microsoft.com/dotnet/sdk:9.0 + environment: + CI_WORKSPACE: "/drone/src" + commands: + - curl https://get.docker.com | bash + - dotnet test --no-build + volumes: + - name: dockersock + path: /var/run + depends_on: + - compile - name: docker build and push image: docker:24.0.7 -- 2.49.1 From 108960d074c246f7b39bde914540476d66373f8a Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Dec 2024 17:24:06 +0100 Subject: [PATCH 055/150] Add debug --- .drone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.drone.yml b/.drone.yml index d505daf..f71e36a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -40,6 +40,7 @@ steps: CI_WORKSPACE: "/drone/src" commands: - curl https://get.docker.com | bash + - docker ps -a - dotnet test --no-build volumes: - name: dockersock -- 2.49.1 From b3ca1ba7037426f7a0d9ebb2ab872bb5c1c3ba21 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Dec 2024 17:42:12 +0100 Subject: [PATCH 056/150] Remove experimental own test step --- .drone.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.drone.yml b/.drone.yml index f71e36a..01f32d3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -34,20 +34,6 @@ steps: depends_on: - compile - - name: test2 - image: mcr.microsoft.com/dotnet/sdk:9.0 - environment: - CI_WORKSPACE: "/drone/src" - commands: - - curl https://get.docker.com | bash - - docker ps -a - - dotnet test --no-build - volumes: - - name: dockersock - path: /var/run - depends_on: - - compile - - name: docker build and push image: docker:24.0.7 commands: -- 2.49.1 From d91b837e445dae38bbeeb5ecb64194697e7fedad Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 12 Jun 2025 17:43:22 +0200 Subject: [PATCH 057/150] Update packages, use explicit type, use Microsoft OpenApi package --- .../Constants.cs | 2 +- .../Vegasco.Server.AppHost.Shared.csproj | 6 ++ src/Vegasco.Server.AppHost/Program.cs | 4 +- .../Vegasco.Server.AppHost.csproj | 7 ++- .../Extensions.cs | 2 +- .../Vegasco.Server.ServiceDefaults.csproj | 17 +++--- src/WebApi/Authentication/UserAccessor.cs | 4 +- src/WebApi/Cars/CreateCar.cs | 4 +- src/WebApi/Cars/DeleteCar.cs | 2 +- .../Common/DependencyInjectionExtensions.cs | 29 ++++------ src/WebApi/Common/FluentValidationOptions.cs | 3 +- src/WebApi/Common/StartupExtensions.cs | 13 +---- src/WebApi/Common/ValidatorExtensions.cs | 6 +- src/WebApi/Endpoints/EndpointExtensions.cs | 16 ------ src/WebApi/Endpoints/IEndpoint.cs | 6 -- .../OpenApi/ConfigureSwaggerGenOptions.cs | 56 ------------------- .../Endpoints/OpenApi/SwaggerDocConstants.cs | 6 -- .../Persistence/ApplyMigrationsService.cs | 14 ++--- src/WebApi/WebApi.csproj | 29 +++++----- .../Cars/CreateCarTests.cs | 6 +- .../Cars/DeleteCarTests.cs | 8 +-- .../Cars/GetCarTests.cs | 8 +-- .../Cars/UpdateCarTests.cs | 18 +++--- .../Consumptions/GetConsumptionTests.cs | 2 +- .../Consumptions/UpdateConsumptionTests.cs | 6 +- ...TestUserAlwaysAuthorizedPolicyEvaluator.cs | 2 +- .../WebApi.Tests.Integration.csproj | 24 ++++---- .../Authentication/UserAccessorTests.cs | 18 +++--- .../Cars/CreateCarRequestValidatorTests.cs | 15 ++--- .../Cars/UpdateCarRequestValidatorTests.cs | 15 ++--- .../WebApi.Tests.Unit.csproj | 14 ++--- vegasco-server.slnx | 6 +- 32 files changed, 138 insertions(+), 230 deletions(-) delete mode 100644 src/WebApi/Endpoints/IEndpoint.cs delete mode 100644 src/WebApi/Endpoints/OpenApi/ConfigureSwaggerGenOptions.cs delete mode 100644 src/WebApi/Endpoints/OpenApi/SwaggerDocConstants.cs diff --git a/src/Vegasco.Server.AppHost.Shared/Constants.cs b/src/Vegasco.Server.AppHost.Shared/Constants.cs index d9a0f57..cdd67eb 100644 --- a/src/Vegasco.Server.AppHost.Shared/Constants.cs +++ b/src/Vegasco.Server.AppHost.Shared/Constants.cs @@ -11,6 +11,6 @@ public static class Constants { public const string ServiceName = "postgres"; - public const string Name = "vegasco"; + public const string Name = "vegasco-database"; } } diff --git a/src/Vegasco.Server.AppHost.Shared/Vegasco.Server.AppHost.Shared.csproj b/src/Vegasco.Server.AppHost.Shared/Vegasco.Server.AppHost.Shared.csproj index 125f4c9..5808fe7 100644 --- a/src/Vegasco.Server.AppHost.Shared/Vegasco.Server.AppHost.Shared.csproj +++ b/src/Vegasco.Server.AppHost.Shared/Vegasco.Server.AppHost.Shared.csproj @@ -6,4 +6,10 @@ enable + + + 3.7.115 + + + diff --git a/src/Vegasco.Server.AppHost/Program.cs b/src/Vegasco.Server.AppHost/Program.cs index bda0036..6c19fd7 100644 --- a/src/Vegasco.Server.AppHost/Program.cs +++ b/src/Vegasco.Server.AppHost/Program.cs @@ -1,8 +1,8 @@ using Vegasco.Server.AppHost.Shared; -var builder = DistributedApplication.CreateBuilder(args); +IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder(args); -var postgres = builder.AddPostgres(Constants.Database.ServiceName) +IResourceBuilder postgres = builder.AddPostgres(Constants.Database.ServiceName) .WithDataVolume() .AddDatabase(Constants.Database.Name); diff --git a/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj b/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj index c22dc1c..70315a2 100644 --- a/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj +++ b/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj @@ -12,8 +12,11 @@ - - + + + + 3.7.115 + diff --git a/src/Vegasco.Server.ServiceDefaults/Extensions.cs b/src/Vegasco.Server.ServiceDefaults/Extensions.cs index 13151bf..5984246 100644 --- a/src/Vegasco.Server.ServiceDefaults/Extensions.cs +++ b/src/Vegasco.Server.ServiceDefaults/Extensions.cs @@ -72,7 +72,7 @@ public static class Extensions private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder { - var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + bool useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); if (useOtlpExporter) { diff --git a/src/Vegasco.Server.ServiceDefaults/Vegasco.Server.ServiceDefaults.csproj b/src/Vegasco.Server.ServiceDefaults/Vegasco.Server.ServiceDefaults.csproj index 24b1b4f..f5a831a 100644 --- a/src/Vegasco.Server.ServiceDefaults/Vegasco.Server.ServiceDefaults.csproj +++ b/src/Vegasco.Server.ServiceDefaults/Vegasco.Server.ServiceDefaults.csproj @@ -10,13 +10,16 @@ - - - - - - - + + + + + + + + + 3.7.115 + diff --git a/src/WebApi/Authentication/UserAccessor.cs b/src/WebApi/Authentication/UserAccessor.cs index 6e69d1a..0b16e67 100644 --- a/src/WebApi/Authentication/UserAccessor.cs +++ b/src/WebApi/Authentication/UserAccessor.cs @@ -47,14 +47,14 @@ public sealed class UserAccessor private string GetClaimValue(string claimType) { - var httpContext = _httpContextAccessor.HttpContext; + HttpContext? httpContext = _httpContextAccessor.HttpContext; if (httpContext is null) { ThrowForMissingHttpContext(); } - var claimValue = httpContext.User.FindFirstValue(claimType); + string? claimValue = httpContext.User.FindFirstValue(claimType); if (string.IsNullOrWhiteSpace(claimValue)) { diff --git a/src/WebApi/Cars/CreateCar.cs b/src/WebApi/Cars/CreateCar.cs index ebcb7e2..b968b85 100644 --- a/src/WebApi/Cars/CreateCar.cs +++ b/src/WebApi/Cars/CreateCar.cs @@ -42,9 +42,9 @@ public static class CreateCar return TypedResults.BadRequest(new HttpValidationProblemDetails(failedValidations.ToCombinedDictionary())); } - var userId = userAccessor.GetUserId(); + string userId = userAccessor.GetUserId(); - var user = await dbContext.Users.FindAsync([userId], cancellationToken: cancellationToken); + User? user = await dbContext.Users.FindAsync([userId], cancellationToken: cancellationToken); if (user is null) { user = new User diff --git a/src/WebApi/Cars/DeleteCar.cs b/src/WebApi/Cars/DeleteCar.cs index 05048d7..638ed4b 100644 --- a/src/WebApi/Cars/DeleteCar.cs +++ b/src/WebApi/Cars/DeleteCar.cs @@ -16,7 +16,7 @@ public static class DeleteCar ApplicationDbContext dbContext, CancellationToken cancellationToken) { - var car = await dbContext.Cars.FindAsync([new CarId(id)], cancellationToken: cancellationToken); + Car? car = await dbContext.Cars.FindAsync([new CarId(id)], cancellationToken: cancellationToken); if (car is null) { diff --git a/src/WebApi/Common/DependencyInjectionExtensions.cs b/src/WebApi/Common/DependencyInjectionExtensions.cs index 7c7e8a4..7bd4fdf 100644 --- a/src/WebApi/Common/DependencyInjectionExtensions.cs +++ b/src/WebApi/Common/DependencyInjectionExtensions.cs @@ -3,8 +3,6 @@ using FluentValidation; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Options; using Vegasco.WebApi.Authentication; -using Vegasco.WebApi.Endpoints; -using Vegasco.WebApi.Endpoints.OpenApi; using Vegasco.WebApi.Persistence; namespace Vegasco.WebApi.Common; @@ -14,14 +12,12 @@ public static class DependencyInjectionExtensions /// /// Adds all the WebApi related services to the Dependency Injection container. /// - /// - /// - /// + /// public static void AddWebApiServices(this IHostApplicationBuilder builder) { builder.Services .AddMiscellaneousServices() - .AddOpenApi() + .AddCustomOpenApi() .AddApiVersioning() .AddAuthenticationAndAuthorization(builder.Environment); @@ -38,7 +34,6 @@ public static class DependencyInjectionExtensions ], ServiceLifetime.Singleton); services.AddHealthChecks(); - services.AddEndpointsFromAssemblyContaining(); services.AddHttpContextAccessor(); @@ -47,32 +42,30 @@ public static class DependencyInjectionExtensions return services; } - private static IServiceCollection AddOpenApi(this IServiceCollection services) + private static IServiceCollection AddCustomOpenApi(this IServiceCollection services) { - services.ConfigureOptions(); - services.AddEndpointsApiExplorer(); - services.AddSwaggerGen(o => + services.AddOpenApi(o => { - o.CustomSchemaIds(type => + o.CreateSchemaReferenceId = jsonTypeInfo => { - if (string.IsNullOrEmpty(type.FullName)) + if (string.IsNullOrEmpty(jsonTypeInfo.Type.FullName)) { - return type.Name; + return jsonTypeInfo.Type.Name; } - var fullClassName = type.FullName; + string? fullClassName = jsonTypeInfo.Type.FullName; - if (!string.IsNullOrEmpty(type.Namespace)) + if (!string.IsNullOrEmpty(jsonTypeInfo.Type.Namespace)) { fullClassName = fullClassName - .Replace(type.Namespace, "") + .Replace(jsonTypeInfo.Type.Namespace, "") .TrimStart('.'); } fullClassName = fullClassName.Replace('+', '_'); return fullClassName; - }); + }; }); return services; diff --git a/src/WebApi/Common/FluentValidationOptions.cs b/src/WebApi/Common/FluentValidationOptions.cs index 141e2e7..3131736 100644 --- a/src/WebApi/Common/FluentValidationOptions.cs +++ b/src/WebApi/Common/FluentValidationOptions.cs @@ -1,4 +1,5 @@ using FluentValidation; +using FluentValidation.Results; using Microsoft.Extensions.Options; namespace Vegasco.WebApi.Common; @@ -25,7 +26,7 @@ public class FluentValidationOptions : IValidateOptions ArgumentNullException.ThrowIfNull(options); - var failedValidations = _validators.ValidateAllAsync(options).Result; + List failedValidations = _validators.ValidateAllAsync(options).Result; if (failedValidations.Count == 0) { return ValidateOptionsResult.Success; diff --git a/src/WebApi/Common/StartupExtensions.cs b/src/WebApi/Common/StartupExtensions.cs index 6b2a7fd..92c5ee7 100644 --- a/src/WebApi/Common/StartupExtensions.cs +++ b/src/WebApi/Common/StartupExtensions.cs @@ -45,18 +45,7 @@ internal static class StartupExtensions if (app.Environment.IsDevelopment()) { - app.UseSwagger(); - app.UseSwaggerUI(o => - { - // Create a Swagger endpoint for each API version - IReadOnlyList apiVersions = app.DescribeApiVersions(); - foreach (ApiVersionDescription apiVersionDescription in apiVersions) - { - string url = $"/swagger/{apiVersionDescription.GroupName}/swagger.json"; - string name = apiVersionDescription.GroupName.ToUpperInvariant(); - o.SwaggerEndpoint(url, name); - } - }); + app.MapOpenApi(); } return app; diff --git a/src/WebApi/Common/ValidatorExtensions.cs b/src/WebApi/Common/ValidatorExtensions.cs index 64e04f5..f574e18 100644 --- a/src/WebApi/Common/ValidatorExtensions.cs +++ b/src/WebApi/Common/ValidatorExtensions.cs @@ -15,7 +15,7 @@ public static class ValidatorExtensions /// The failed validation results. public static async Task> ValidateAllAsync(this IEnumerable> validators, T instance, CancellationToken cancellationToken = default) { - var validationTasks = validators + List> validationTasks = validators .Select(validator => validator.ValidateAsync(instance, cancellationToken)) .ToList(); @@ -34,7 +34,7 @@ public static class ValidatorExtensions // Use a hash set to avoid duplicate error messages. Dictionary> combinedErrors = []; - foreach (var error in validationResults.SelectMany(x => x.Errors)) + foreach (ValidationFailure? error in validationResults.SelectMany(x => x.Errors)) { if (!combinedErrors.TryGetValue(error.PropertyName, out HashSet? value)) { @@ -54,7 +54,7 @@ public static class ValidatorExtensions { builder.Services.AddTransient>(serviceProvider => { - var validators = serviceProvider.GetServices>() ?? []; + IEnumerable> validators = serviceProvider.GetServices>() ?? []; return new FluentValidationOptions(builder.Name, validators); }); return builder; diff --git a/src/WebApi/Endpoints/EndpointExtensions.cs b/src/WebApi/Endpoints/EndpointExtensions.cs index 5cc9c9c..599ff6d 100644 --- a/src/WebApi/Endpoints/EndpointExtensions.cs +++ b/src/WebApi/Endpoints/EndpointExtensions.cs @@ -10,22 +10,6 @@ namespace Vegasco.WebApi.Endpoints; public static class EndpointExtensions { - public static IServiceCollection AddEndpointsFromAssemblyContaining(this IServiceCollection services) - { - var assembly = typeof(T).Assembly; - - ServiceDescriptor[] serviceDescriptors = assembly - .DefinedTypes - .Where(type => type is { IsAbstract: false, IsInterface: false } && - type.IsAssignableTo(typeof(IEndpoint))) - .Select(type => ServiceDescriptor.Transient(typeof(IEndpoint), type)) - .ToArray(); - - services.TryAddEnumerable(serviceDescriptors); - - return services; - } - public static void MapEndpoints(this IEndpointRouteBuilder builder) { ApiVersionSet apiVersionSet = builder.NewApiVersionSet() diff --git a/src/WebApi/Endpoints/IEndpoint.cs b/src/WebApi/Endpoints/IEndpoint.cs deleted file mode 100644 index 15e0c50..0000000 --- a/src/WebApi/Endpoints/IEndpoint.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Vegasco.WebApi.Endpoints; - -public interface IEndpoint -{ - void MapEndpoint(IEndpointRouteBuilder builder); -} diff --git a/src/WebApi/Endpoints/OpenApi/ConfigureSwaggerGenOptions.cs b/src/WebApi/Endpoints/OpenApi/ConfigureSwaggerGenOptions.cs deleted file mode 100644 index 571dde8..0000000 --- a/src/WebApi/Endpoints/OpenApi/ConfigureSwaggerGenOptions.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Asp.Versioning.ApiExplorer; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace Vegasco.WebApi.Endpoints.OpenApi; - -/// -/// Registers each api version as its own swagger document. -/// -/// -public class ConfigureSwaggerGenOptions( - IApiVersionDescriptionProvider versionDescriptionProvider) - : IConfigureNamedOptions -{ - private readonly IApiVersionDescriptionProvider _versionDescriptionProvider = versionDescriptionProvider; - - public void Configure(SwaggerGenOptions options) - { - foreach (ApiVersionDescription description in _versionDescriptionProvider.ApiVersionDescriptions) - { - OpenApiSecurityScheme securityScheme = new() - { - Name = "Bearer", - In = ParameterLocation.Header, - Type = SecuritySchemeType.Http, - Scheme = "bearer", - Reference = new OpenApiReference - { - Id = IdentityConstants.BearerScheme, - Type = ReferenceType.SecurityScheme - } - }; - options.AddSecurityDefinition(securityScheme.Reference.Id, securityScheme); - - options.AddSecurityRequirement(new OpenApiSecurityRequirement - { - { securityScheme, Array.Empty() } - }); - - OpenApiInfo openApiInfo = new() - { - Title = "Vegasco API", - Version = description.ApiVersion.ToString() - }; - - options.SwaggerDoc(description.GroupName, openApiInfo); - } - } - - public void Configure(string? name, SwaggerGenOptions options) - { - Configure(options); - } -} diff --git a/src/WebApi/Endpoints/OpenApi/SwaggerDocConstants.cs b/src/WebApi/Endpoints/OpenApi/SwaggerDocConstants.cs deleted file mode 100644 index e88de76..0000000 --- a/src/WebApi/Endpoints/OpenApi/SwaggerDocConstants.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Vegasco.WebApi.Endpoints.OpenApi; - -public static class SwaggerDocConstants -{ - -} diff --git a/src/WebApi/Persistence/ApplyMigrationsService.cs b/src/WebApi/Persistence/ApplyMigrationsService.cs index a55de3a..c135ed1 100644 --- a/src/WebApi/Persistence/ApplyMigrationsService.cs +++ b/src/WebApi/Persistence/ApplyMigrationsService.cs @@ -2,18 +2,14 @@ namespace Vegasco.WebApi.Persistence; -public class ApplyMigrationsService : IHostedService +public class ApplyMigrationsService(ILogger logger, IServiceScopeFactory scopeFactory) + : IHostedService { - private readonly IServiceScopeFactory _scopeFactory; - - public ApplyMigrationsService(IServiceScopeFactory scopeFactory) - { - _scopeFactory = scopeFactory; - } - public async Task StartAsync(CancellationToken cancellationToken) { - using IServiceScope scope = _scopeFactory.CreateScope(); + logger.LogInformation("Starting migrations"); + + using IServiceScope scope = scopeFactory.CreateScope(); await using var dbContext = scope.ServiceProvider.GetRequiredService(); await dbContext.Database.MigrateAsync(cancellationToken); } diff --git a/src/WebApi/WebApi.csproj b/src/WebApi/WebApi.csproj index 8f2416f..9ba8886 100644 --- a/src/WebApi/WebApi.csproj +++ b/src/WebApi/WebApi.csproj @@ -13,25 +13,24 @@ - - - - - - + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - - - - - + + + + + + + - @@ -40,7 +39,7 @@ - + diff --git a/tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs b/tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs index b9226af..95d2c03 100644 --- a/tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs +++ b/tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs @@ -28,10 +28,10 @@ public class CreateCarTests : IAsyncLifetime public async Task CreateCar_ShouldCreateCar_WhenRequestIsValid() { // Arrange - var createCarRequest = _carFaker.CreateCarRequest(); + CreateCar.Request createCarRequest = _carFaker.CreateCarRequest(); // Act - var response = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.Created); @@ -49,7 +49,7 @@ public class CreateCarTests : IAsyncLifetime var createCarRequest = new CreateCar.Request(""); // Act - var response = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); diff --git a/tests/WebApi.Tests.Integration/Cars/DeleteCarTests.cs b/tests/WebApi.Tests.Integration/Cars/DeleteCarTests.cs index e672d0f..160d96e 100644 --- a/tests/WebApi.Tests.Integration/Cars/DeleteCarTests.cs +++ b/tests/WebApi.Tests.Integration/Cars/DeleteCarTests.cs @@ -30,7 +30,7 @@ public class DeleteCarTests : IAsyncLifetime var randomCarId = Guid.NewGuid(); // Act - var response = await _factory.HttpClient.DeleteAsync($"v1/cars/{randomCarId}"); + HttpResponseMessage response = await _factory.HttpClient.DeleteAsync($"v1/cars/{randomCarId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); @@ -40,13 +40,13 @@ public class DeleteCarTests : IAsyncLifetime public async Task DeleteCar_ShouldDeleteCar_WhenCarExists() { // Arrange - var createCarRequest = _carFaker.CreateCarRequest(); - var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + CreateCar.Request createCarRequest = _carFaker.CreateCarRequest(); + HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); // Act - var response = await _factory.HttpClient.DeleteAsync($"v1/cars/{createdCar!.Id}"); + HttpResponseMessage response = await _factory.HttpClient.DeleteAsync($"v1/cars/{createdCar!.Id}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NoContent); diff --git a/tests/WebApi.Tests.Integration/Cars/GetCarTests.cs b/tests/WebApi.Tests.Integration/Cars/GetCarTests.cs index af2dcfa..f425919 100644 --- a/tests/WebApi.Tests.Integration/Cars/GetCarTests.cs +++ b/tests/WebApi.Tests.Integration/Cars/GetCarTests.cs @@ -24,7 +24,7 @@ public class GetCarTests : IAsyncLifetime var randomCarId = Guid.NewGuid(); // Act - var response = await _factory.HttpClient.GetAsync($"v1/cars/{randomCarId}"); + HttpResponseMessage response = await _factory.HttpClient.GetAsync($"v1/cars/{randomCarId}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); @@ -34,13 +34,13 @@ public class GetCarTests : IAsyncLifetime public async Task GetCar_ShouldReturnCar_WhenCarExists() { // Arrange - var createCarRequest = _carFaker.CreateCarRequest(); - var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + CreateCar.Request createCarRequest = _carFaker.CreateCarRequest(); + HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); // Act - var response = await _factory.HttpClient.GetAsync($"v1/cars/{createdCar!.Id}"); + HttpResponseMessage response = await _factory.HttpClient.GetAsync($"v1/cars/{createdCar!.Id}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); diff --git a/tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs b/tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs index 56a81e7..ded5967 100644 --- a/tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs +++ b/tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs @@ -28,15 +28,15 @@ public class UpdateCarTests : IAsyncLifetime public async Task UpdateCar_ShouldUpdateCar_WhenCarExistsAndRequestIsValid() { // Arrange - var createCarRequest = _carFaker.CreateCarRequest(); - var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + CreateCar.Request createCarRequest = _carFaker.CreateCarRequest(); + HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); - var updateCarRequest = _carFaker.UpdateCarRequest(); + UpdateCar.Request updateCarRequest = _carFaker.UpdateCarRequest(); // Act - var response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{createdCar!.Id}", updateCarRequest); + HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{createdCar!.Id}", updateCarRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); @@ -54,15 +54,15 @@ public class UpdateCarTests : IAsyncLifetime public async Task UpdateCar_ShouldReturnValidationProblems_WhenRequestIsNotValid() { // Arrange - var createCarRequest = _carFaker.CreateCarRequest(); - var createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + CreateCar.Request createCarRequest = _carFaker.CreateCarRequest(); + HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); var updateCarRequest = new UpdateCar.Request(""); // Act - var response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{createdCar!.Id}", updateCarRequest); + HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{createdCar!.Id}", updateCarRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -79,11 +79,11 @@ public class UpdateCarTests : IAsyncLifetime public async Task UpdateCar_ShouldReturnNotFound_WhenNoCarWithIdExists() { // Arrange - var updateCarRequest = _carFaker.UpdateCarRequest(); + UpdateCar.Request updateCarRequest = _carFaker.UpdateCarRequest(); var randomCarId = Guid.NewGuid(); // Act - var response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{randomCarId}", updateCarRequest); + HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{randomCarId}", updateCarRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.NotFound); diff --git a/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionTests.cs b/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionTests.cs index 2e490f9..6448776 100644 --- a/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionTests.cs +++ b/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionTests.cs @@ -35,7 +35,7 @@ public class GetConsumptionTests : IAsyncLifetime using HttpResponseMessage response = await _factory.HttpClient.GetAsync($"v1/consumptions/{createdConsumption.Id}"); // Assert - var content = await response.Content.ReadAsStringAsync(); + string content = await response.Content.ReadAsStringAsync(); response.StatusCode.Should().Be(HttpStatusCode.OK); var consumption = await response.Content.ReadFromJsonAsync(); consumption.Should().BeEquivalentTo(createdConsumption); diff --git a/tests/WebApi.Tests.Integration/Consumptions/UpdateConsumptionTests.cs b/tests/WebApi.Tests.Integration/Consumptions/UpdateConsumptionTests.cs index 723f7eb..3999b1d 100644 --- a/tests/WebApi.Tests.Integration/Consumptions/UpdateConsumptionTests.cs +++ b/tests/WebApi.Tests.Integration/Consumptions/UpdateConsumptionTests.cs @@ -37,7 +37,7 @@ public class UpdateConsumptionTests : IAsyncLifetime using HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/consumptions/{createdConsumption.Id}", updateConsumptionRequest); // Assert - var content = await response.Content.ReadAsStringAsync(); + string content = await response.Content.ReadAsStringAsync(); response.StatusCode.Should().Be(HttpStatusCode.OK); var updatedConsumption = await response.Content.ReadFromJsonAsync(); updatedConsumption.Should().BeEquivalentTo(updateConsumptionRequest, o => o.ExcludingMissingMembers()); @@ -65,7 +65,7 @@ public class UpdateConsumptionTests : IAsyncLifetime using HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/consumptions/{randomGuid}", updateConsumptionRequest); // Assert - var content = await response.Content.ReadAsStringAsync(); + string content = await response.Content.ReadAsStringAsync(); response.StatusCode.Should().Be(HttpStatusCode.BadRequest); var validationProblemDetails = await response.Content.ReadFromJsonAsync(); validationProblemDetails!.Errors.Keys.Should().Contain(x => @@ -86,7 +86,7 @@ public class UpdateConsumptionTests : IAsyncLifetime using HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/consumptions/{randomGuid}", updateConsumptionRequest); // Assert - var content = await response.Content.ReadAsStringAsync(); + string content = await response.Content.ReadAsStringAsync(); response.StatusCode.Should().Be(HttpStatusCode.NotFound); _dbContext.Consumptions.Should().NotContainEquivalentOf(updateConsumptionRequest); diff --git a/tests/WebApi.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs b/tests/WebApi.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs index ce19bdf..787d255 100644 --- a/tests/WebApi.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs +++ b/tests/WebApi.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs @@ -26,7 +26,7 @@ public sealed class TestUserAlwaysAuthorizedPolicyEvaluator : IPolicyEvaluator ClaimsIdentity identity = new(claims, JwtBearerDefaults.AuthenticationScheme); ClaimsPrincipal principal = new(identity); AuthenticationTicket ticket = new(principal, JwtBearerDefaults.AuthenticationScheme); - var result = AuthenticateResult.Success(ticket); + AuthenticateResult result = AuthenticateResult.Success(ticket); return Task.FromResult(result); ; } diff --git a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj index 4d73780..ebfdb83 100644 --- a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj +++ b/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj @@ -10,21 +10,21 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -40,7 +40,7 @@ - + diff --git a/tests/WebApi.Tests.Unit/Authentication/UserAccessorTests.cs b/tests/WebApi.Tests.Unit/Authentication/UserAccessorTests.cs index c32f769..0e5a68c 100644 --- a/tests/WebApi.Tests.Unit/Authentication/UserAccessorTests.cs +++ b/tests/WebApi.Tests.Unit/Authentication/UserAccessorTests.cs @@ -50,7 +50,7 @@ public sealed class UserAccessorTests // Arrange // Act - var result = _sut.GetUsername(); + string result = _sut.GetUsername(); // Assert result.Should().Be(_defaultUsername); @@ -67,7 +67,7 @@ public sealed class UserAccessorTests ])); // Act - var result = _sut.GetUsername(); + string result = _sut.GetUsername(); // Assert result.Should().Be(_defaultUsername); @@ -81,7 +81,7 @@ public sealed class UserAccessorTests _options.ClearReceivedCalls(); // Act - var result = _sut.GetUsername(); + string result = _sut.GetUsername(); // Assert result.Should().Be(_defaultUsername); @@ -95,7 +95,7 @@ public sealed class UserAccessorTests _httpContextAccessor.HttpContext = null; // Act - var action = () => _sut.GetUsername(); + Func action = () => _sut.GetUsername(); // Assert action.Should().ThrowExactly() @@ -109,7 +109,7 @@ public sealed class UserAccessorTests _httpContextAccessor.HttpContext!.User = new ClaimsPrincipal(); // Act - var action = () => _sut.GetUsername(); + Func action = () => _sut.GetUsername(); // Assert action.Should().ThrowExactly() @@ -126,7 +126,7 @@ public sealed class UserAccessorTests // Arrange // Act - var result = _sut.GetUserId(); + string result = _sut.GetUserId(); // Assert result.Should().Be(_defaultId); @@ -140,7 +140,7 @@ public sealed class UserAccessorTests _options.ClearReceivedCalls(); // Act - var result = _sut.GetUserId(); + string result = _sut.GetUserId(); // Assert result.Should().Be(_defaultId); @@ -154,7 +154,7 @@ public sealed class UserAccessorTests _httpContextAccessor.HttpContext = null; // Act - var action = () => _sut.GetUserId(); + Func action = () => _sut.GetUserId(); // Assert action.Should().ThrowExactly() @@ -168,7 +168,7 @@ public sealed class UserAccessorTests _httpContextAccessor.HttpContext!.User = new ClaimsPrincipal(); // Act - var action = () => _sut.GetUserId(); + Func action = () => _sut.GetUserId(); // Assert action.Should().ThrowExactly() diff --git a/tests/WebApi.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs b/tests/WebApi.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs index 127da32..2185cd3 100644 --- a/tests/WebApi.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs +++ b/tests/WebApi.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using FluentValidation.Results; using Vegasco.WebApi.Cars; namespace WebApi.Tests.Unit.Cars; @@ -15,7 +16,7 @@ public sealed class CreateCarRequestValidatorTests // Arrange // Act - var result = await _sut.ValidateAsync(_validRequest); + ValidationResult? result = await _sut.ValidateAsync(_validRequest); // Assert result.IsValid.Should().BeTrue(); @@ -27,10 +28,10 @@ public sealed class CreateCarRequestValidatorTests public async Task ValidateAsync_ShouldBeValid_WhenNameIsJustWithinTheLimits(int nameLength) { // Arrange - var request = _validRequest with { Name = new string('s', nameLength) }; + CreateCar.Request request = _validRequest with { Name = new string('s', nameLength) }; // Act - var result = await _sut.ValidateAsync(request); + ValidationResult? result = await _sut.ValidateAsync(request); // Assert result.IsValid.Should().BeTrue(); @@ -40,10 +41,10 @@ public sealed class CreateCarRequestValidatorTests public async Task ValidateAsync_ShouldNotBeValid_WhenNameIsEmpty() { // Arrange - var request = _validRequest with { Name = "" }; + CreateCar.Request request = _validRequest with { Name = "" }; // Act - var result = await _sut.ValidateAsync(request); + ValidationResult? result = await _sut.ValidateAsync(request); // Assert result.IsValid.Should().BeFalse(); @@ -57,10 +58,10 @@ public sealed class CreateCarRequestValidatorTests { // Arrange const int nameMaxLength = 50; - var request = _validRequest with { Name = new string('s', nameMaxLength + 1) }; + CreateCar.Request request = _validRequest with { Name = new string('s', nameMaxLength + 1) }; // Act - var result = await _sut.ValidateAsync(request); + ValidationResult? result = await _sut.ValidateAsync(request); // Assert result.IsValid.Should().BeFalse(); diff --git a/tests/WebApi.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs b/tests/WebApi.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs index 8e2d1e0..366fd5a 100644 --- a/tests/WebApi.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs +++ b/tests/WebApi.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using FluentValidation.Results; using Vegasco.WebApi.Cars; namespace WebApi.Tests.Unit.Cars; @@ -15,7 +16,7 @@ public sealed class UpdateCarRequestValidatorTests // Arrange // Act - var result = await _sut.ValidateAsync(_validRequest); + ValidationResult? result = await _sut.ValidateAsync(_validRequest); // Assert result.IsValid.Should().BeTrue(); @@ -27,10 +28,10 @@ public sealed class UpdateCarRequestValidatorTests public async Task ValidateAsync_ShouldBeValid_WhenNameIsJustWithinTheLimits(int nameLength) { // Arrange - var request = _validRequest with { Name = new string('s', nameLength) }; + UpdateCar.Request request = _validRequest with { Name = new string('s', nameLength) }; // Act - var result = await _sut.ValidateAsync(request); + ValidationResult? result = await _sut.ValidateAsync(request); // Assert result.IsValid.Should().BeTrue(); @@ -40,10 +41,10 @@ public sealed class UpdateCarRequestValidatorTests public async Task ValidateAsync_ShouldNotBeValid_WhenNameIsEmpty() { // Arrange - var request = _validRequest with { Name = "" }; + UpdateCar.Request request = _validRequest with { Name = "" }; // Act - var result = await _sut.ValidateAsync(request); + ValidationResult? result = await _sut.ValidateAsync(request); // Assert result.IsValid.Should().BeFalse(); @@ -57,10 +58,10 @@ public sealed class UpdateCarRequestValidatorTests { // Arrange const int nameMaxLength = 50; - var request = _validRequest with { Name = new string('s', nameMaxLength + 1) }; + UpdateCar.Request request = _validRequest with { Name = new string('s', nameMaxLength + 1) }; // Act - var result = await _sut.ValidateAsync(request); + ValidationResult? result = await _sut.ValidateAsync(request); // Assert result.IsValid.Should().BeFalse(); diff --git a/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj b/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj index e31c3b3..71caaf1 100644 --- a/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj +++ b/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj @@ -10,16 +10,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -34,7 +34,7 @@ - + diff --git a/vegasco-server.slnx b/vegasco-server.slnx index 5cc0fb0..a928535 100644 --- a/vegasco-server.slnx +++ b/vegasco-server.slnx @@ -6,8 +6,8 @@ - - + + @@ -15,4 +15,4 @@ - + \ No newline at end of file -- 2.49.1 From 9d71c86474461df38783a329cb318364ae1c84e4 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 12 Jun 2025 17:58:50 +0200 Subject: [PATCH 058/150] Fix broken swagger route --- src/WebApi/Common/StartupExtensions.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/WebApi/Common/StartupExtensions.cs b/src/WebApi/Common/StartupExtensions.cs index 92c5ee7..54f077b 100644 --- a/src/WebApi/Common/StartupExtensions.cs +++ b/src/WebApi/Common/StartupExtensions.cs @@ -1,5 +1,4 @@ -using Asp.Versioning.ApiExplorer; -using Microsoft.AspNetCore.Localization; +using Microsoft.AspNetCore.Localization; using System.Globalization; using Vegasco.WebApi.Endpoints; @@ -45,7 +44,7 @@ internal static class StartupExtensions if (app.Environment.IsDevelopment()) { - app.MapOpenApi(); + app.MapOpenApi("/swagger/{documentName}/swagger.json"); } return app; -- 2.49.1 From a1999bfe4164abe7622c5f99e0883b94262cc456 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 12 Jun 2025 18:22:37 +0200 Subject: [PATCH 059/150] Rename WebApi project to Vegasco.Server.Api And update all references including comments etc. --- Dockerfile | 12 ++--- README.md | 2 +- .../Assembly.cs | 0 .../Authentication/JwtOptions.cs | 2 +- .../Authentication/UserAccessor.cs | 2 +- .../Cars/Car.cs | 6 +-- .../Cars/CarId.cs | 2 +- .../Cars/CreateCar.cs | 10 ++-- .../Cars/DeleteCar.cs | 4 +- .../Cars/GetCar.cs | 4 +- .../Cars/GetCars.cs | 4 +- .../Cars/UpdateCar.cs | 8 +-- .../Common/Constants.cs | 2 +- .../Common/DependencyInjectionExtensions.cs | 15 +++--- .../Common/FluentValidationOptions.cs | 2 +- src/Vegasco.Server.Api/Common/IApiMarker.cs | 3 ++ .../Common/StartupExtensions.cs | 7 +-- .../Common/ValidatorExtensions.cs | 4 +- .../Consumptions/Consumption.cs | 4 +- .../Consumptions/ConsumptionId.cs | 2 +- .../Consumptions/CreateConsumption.cs | 8 +-- .../Consumptions/DeleteConsumptions.cs | 4 +- .../Consumptions/GetConsumption.cs | 4 +- .../Consumptions/GetConsumptions.cs | 4 +- .../Consumptions/UpdateConsumption.cs | 6 +-- .../Endpoints/EndpointExtensions.cs | 11 ++-- .../Info/GetServerInfo.cs | 2 +- .../Persistence/ApplicationDbContext.cs | 12 ++--- .../Persistence/ApplyMigrationsService.cs | 2 +- .../20240818105918_Initial.Designer.cs | 23 +++++---- .../Migrations/20240818105918_Initial.cs | 5 +- .../ApplicationDbContextModelSnapshot.cs | 23 +++++---- src/{WebApi => Vegasco.Server.Api}/Program.cs | 2 +- .../Properties/launchSettings.json | 0 .../Users/User.cs | 4 +- .../Users/UserTableConfiguration.cs | 2 +- .../Vegasco.Server.Api.csproj} | 2 +- .../appsettings.Development.json | 0 .../appsettings.json | 0 .../Constants.cs | 2 +- src/Vegasco.Server.AppHost/Program.cs | 2 +- .../Vegasco.Server.AppHost.csproj | 2 +- .../Extensions.cs | 4 +- src/WebApi/Common/IWebApiMarker.cs | 3 -- .../CarFaker.cs | 4 +- .../Cars/CreateCarTests.cs | 6 +-- .../Cars/DeleteCarTests.cs | 6 +-- .../Cars/GetCarTests.cs | 4 +- .../Cars/GetCarsTests.cs | 4 +- .../Cars/UpdateCarTests.cs | 6 +-- .../ConsumptionFaker.cs | 4 +- .../Consumptions/CreateConsumptionTests.cs | 8 +-- .../Consumptions/DeleteConsumptionTests.cs | 8 +-- .../Consumptions/GetConsumptionTests.cs | 8 +-- .../Consumptions/GetConsumptionsTests.cs | 8 +-- .../Consumptions/UpdateConsumptionTests.cs | 8 +-- .../FluentAssertionConfiguration.cs | 3 +- .../Info/GetServerInfoTests.cs | 4 +- .../PostgresRespawner.cs | 2 +- .../SharedTestCollection.cs | 2 +- ...TestUserAlwaysAuthorizedPolicyEvaluator.cs | 2 +- ...gasco.Server.Api.Tests.Integration.csproj} | 6 +-- .../WebAppFactory.cs | 8 +-- .../Authentication/UserAccessorTests.cs | 4 +- .../Cars/CreateCarRequestValidatorTests.cs | 4 +- .../Cars/UpdateCarRequestValidatorTests.cs | 4 +- .../CreateConsumptionRequestValidatorTests.cs | 4 +- .../UpdateConsumptionRequestValidatorTests.cs | 4 +- .../Vegasco.Server.Api.Tests.Unit.csproj} | 2 +- vegasco-server.sln | 50 ------------------- vegasco-server.slnx | 6 +-- 71 files changed, 177 insertions(+), 224 deletions(-) rename src/{WebApi => Vegasco.Server.Api}/Assembly.cs (100%) rename src/{WebApi => Vegasco.Server.Api}/Authentication/JwtOptions.cs (91%) rename src/{WebApi => Vegasco.Server.Api}/Authentication/UserAccessor.cs (97%) rename src/{WebApi => Vegasco.Server.Api}/Cars/Car.cs (88%) rename src/{WebApi => Vegasco.Server.Api}/Cars/CarId.cs (68%) rename src/{WebApi => Vegasco.Server.Api}/Cars/CreateCar.cs (89%) rename src/{WebApi => Vegasco.Server.Api}/Cars/DeleteCar.cs (89%) rename src/{WebApi => Vegasco.Server.Api}/Cars/GetCar.cs (89%) rename src/{WebApi => Vegasco.Server.Api}/Cars/GetCars.cs (93%) rename src/{WebApi => Vegasco.Server.Api}/Cars/UpdateCar.cs (90%) rename src/{WebApi => Vegasco.Server.Api}/Common/Constants.cs (79%) rename src/{WebApi => Vegasco.Server.Api}/Common/DependencyInjectionExtensions.cs (88%) rename src/{WebApi => Vegasco.Server.Api}/Common/FluentValidationOptions.cs (96%) create mode 100644 src/Vegasco.Server.Api/Common/IApiMarker.cs rename src/{WebApi => Vegasco.Server.Api}/Common/StartupExtensions.cs (87%) rename src/{WebApi => Vegasco.Server.Api}/Common/ValidatorExtensions.cs (96%) rename src/{WebApi => Vegasco.Server.Api}/Consumptions/Consumption.cs (93%) rename src/{WebApi => Vegasco.Server.Api}/Consumptions/ConsumptionId.cs (66%) rename src/{WebApi => Vegasco.Server.Api}/Consumptions/CreateConsumption.cs (93%) rename src/{WebApi => Vegasco.Server.Api}/Consumptions/DeleteConsumptions.cs (89%) rename src/{WebApi => Vegasco.Server.Api}/Consumptions/GetConsumption.cs (91%) rename src/{WebApi => Vegasco.Server.Api}/Consumptions/GetConsumptions.cs (94%) rename src/{WebApi => Vegasco.Server.Api}/Consumptions/UpdateConsumption.cs (94%) rename src/{WebApi => Vegasco.Server.Api}/Endpoints/EndpointExtensions.cs (81%) rename src/{WebApi => Vegasco.Server.Api}/Info/GetServerInfo.cs (95%) rename src/{WebApi => Vegasco.Server.Api}/Persistence/ApplicationDbContext.cs (60%) rename src/{WebApi => Vegasco.Server.Api}/Persistence/ApplyMigrationsService.cs (93%) rename src/{WebApi => Vegasco.Server.Api}/Persistence/Migrations/20240818105918_Initial.Designer.cs (81%) rename src/{WebApi => Vegasco.Server.Api}/Persistence/Migrations/20240818105918_Initial.cs (96%) rename src/{WebApi => Vegasco.Server.Api}/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs (80%) rename src/{WebApi => Vegasco.Server.Api}/Program.cs (74%) rename src/{WebApi => Vegasco.Server.Api}/Properties/launchSettings.json (100%) rename src/{WebApi => Vegasco.Server.Api}/Users/User.cs (62%) rename src/{WebApi => Vegasco.Server.Api}/Users/UserTableConfiguration.cs (88%) rename src/{WebApi/WebApi.csproj => Vegasco.Server.Api/Vegasco.Server.Api.csproj} (97%) rename src/{WebApi => Vegasco.Server.Api}/appsettings.Development.json (100%) rename src/{WebApi => Vegasco.Server.Api}/appsettings.json (100%) delete mode 100644 src/WebApi/Common/IWebApiMarker.cs rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/CarFaker.cs (79%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/Cars/CreateCarTests.cs (94%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/Cars/DeleteCarTests.cs (93%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/Cars/GetCarTests.cs (94%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/Cars/GetCarsTests.cs (95%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/Cars/UpdateCarTests.cs (96%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/ConsumptionFaker.cs (88%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/Consumptions/CreateConsumptionTests.cs (95%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/Consumptions/DeleteConsumptionTests.cs (94%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/Consumptions/GetConsumptionTests.cs (94%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/Consumptions/GetConsumptionsTests.cs (94%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/Consumptions/UpdateConsumptionTests.cs (96%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/FluentAssertionConfiguration.cs (93%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/Info/GetServerInfoTests.cs (92%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/PostgresRespawner.cs (94%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/SharedTestCollection.cs (76%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/TestUserAlwaysAuthorizedPolicyEvaluator.cs (96%) rename tests/{WebApi.Tests.Integration/WebApi.Tests.Integration.csproj => Vegasco.Server.Api.Tests.Integration/Vegasco.Server.Api.Tests.Integration.csproj} (89%) rename tests/{WebApi.Tests.Integration => Vegasco.Server.Api.Tests.Integration}/WebAppFactory.cs (86%) rename tests/{WebApi.Tests.Unit => Vegasco.Server.Api.Tests.Unit}/Authentication/UserAccessorTests.cs (97%) rename tests/{WebApi.Tests.Unit => Vegasco.Server.Api.Tests.Unit}/Cars/CreateCarRequestValidatorTests.cs (95%) rename tests/{WebApi.Tests.Unit => Vegasco.Server.Api.Tests.Unit}/Cars/UpdateCarRequestValidatorTests.cs (95%) rename tests/{WebApi.Tests.Unit => Vegasco.Server.Api.Tests.Unit}/Consumptions/CreateConsumptionRequestValidatorTests.cs (96%) rename tests/{WebApi.Tests.Unit => Vegasco.Server.Api.Tests.Unit}/Consumptions/UpdateConsumptionRequestValidatorTests.cs (96%) rename tests/{WebApi.Tests.Unit/WebApi.Tests.Unit.csproj => Vegasco.Server.Api.Tests.Unit/Vegasco.Server.Api.Tests.Unit.csproj} (93%) delete mode 100644 vegasco-server.sln diff --git a/Dockerfile b/Dockerfile index a20303b..a72abbb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,18 +10,18 @@ USER app FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src -COPY ["src/WebApi/WebApi.csproj", "src/WebApi/"] -RUN dotnet restore "./src/WebApi/WebApi.csproj" +COPY ["src/Vegasco.Server.Api/Vegasco.Server.Api.csproj", "src/Vegasco.Server.Api/"] +RUN dotnet restore "./src/Vegasco.Server.Api/Vegasco.Server.Api.csproj" COPY . . -WORKDIR "/src/src/WebApi" -RUN dotnet build "./WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/build +WORKDIR "/src/src/Vegasco.Server.Api" +RUN dotnet build "./Vegasco.Server.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build FROM build AS publish ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "./WebApi.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false +RUN dotnet publish "./Vegasco.Server.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app COPY --from=publish /app/publish . HEALTHCHECK --interval=20s --timeout=1s --start-period=10s --retries=3 CMD curl --fail http://localhost:8080/health || exit 1 -ENTRYPOINT ["dotnet", "WebApi.dll"] \ No newline at end of file +ENTRYPOINT ["dotnet", "Vegasco.Server.Api.dll"] \ No newline at end of file diff --git a/README.md b/README.md index aed75ba..54c564d 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ As appsettings.json (or a environment specific appsettings.*.json): ### Running the application The solution uses Aspire to orchestrate the application. Specifically, it introduces sensible service defaults, including but not limited to OpenTelemetry, -creates a Postgres database as a docker container, and starts the WebApi with the correct configuration to communicate with the database. +creates a Postgres database as a docker container, and starts the Api with the correct configuration to communicate with the database. Ensure you have an identity provider set up, for example Keycloak, and configured the relevant options described above. diff --git a/src/WebApi/Assembly.cs b/src/Vegasco.Server.Api/Assembly.cs similarity index 100% rename from src/WebApi/Assembly.cs rename to src/Vegasco.Server.Api/Assembly.cs diff --git a/src/WebApi/Authentication/JwtOptions.cs b/src/Vegasco.Server.Api/Authentication/JwtOptions.cs similarity index 91% rename from src/WebApi/Authentication/JwtOptions.cs rename to src/Vegasco.Server.Api/Authentication/JwtOptions.cs index ae44dbe..bb9073d 100644 --- a/src/WebApi/Authentication/JwtOptions.cs +++ b/src/Vegasco.Server.Api/Authentication/JwtOptions.cs @@ -1,6 +1,6 @@ using FluentValidation; -namespace Vegasco.WebApi.Authentication; +namespace Vegasco.Server.Api.Authentication; public class JwtOptions { diff --git a/src/WebApi/Authentication/UserAccessor.cs b/src/Vegasco.Server.Api/Authentication/UserAccessor.cs similarity index 97% rename from src/WebApi/Authentication/UserAccessor.cs rename to src/Vegasco.Server.Api/Authentication/UserAccessor.cs index 0b16e67..989d7bf 100644 --- a/src/WebApi/Authentication/UserAccessor.cs +++ b/src/Vegasco.Server.Api/Authentication/UserAccessor.cs @@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Security.Claims; -namespace Vegasco.WebApi.Authentication; +namespace Vegasco.Server.Api.Authentication; public sealed class UserAccessor { diff --git a/src/WebApi/Cars/Car.cs b/src/Vegasco.Server.Api/Cars/Car.cs similarity index 88% rename from src/WebApi/Cars/Car.cs rename to src/Vegasco.Server.Api/Cars/Car.cs index 6e72bc4..b506d60 100644 --- a/src/WebApi/Cars/Car.cs +++ b/src/Vegasco.Server.Api/Cars/Car.cs @@ -1,9 +1,9 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Vegasco.WebApi.Consumptions; -using Vegasco.WebApi.Users; +using Vegasco.Server.Api.Consumptions; +using Vegasco.Server.Api.Users; -namespace Vegasco.WebApi.Cars; +namespace Vegasco.Server.Api.Cars; public class Car { diff --git a/src/WebApi/Cars/CarId.cs b/src/Vegasco.Server.Api/Cars/CarId.cs similarity index 68% rename from src/WebApi/Cars/CarId.cs rename to src/Vegasco.Server.Api/Cars/CarId.cs index f901160..86ea23e 100644 --- a/src/WebApi/Cars/CarId.cs +++ b/src/Vegasco.Server.Api/Cars/CarId.cs @@ -1,6 +1,6 @@ using StronglyTypedIds; -namespace Vegasco.WebApi.Cars; +namespace Vegasco.Server.Api.Cars; [StronglyTypedId] public partial struct CarId; diff --git a/src/WebApi/Cars/CreateCar.cs b/src/Vegasco.Server.Api/Cars/CreateCar.cs similarity index 89% rename from src/WebApi/Cars/CreateCar.cs rename to src/Vegasco.Server.Api/Cars/CreateCar.cs index b968b85..d254b6b 100644 --- a/src/WebApi/Cars/CreateCar.cs +++ b/src/Vegasco.Server.Api/Cars/CreateCar.cs @@ -1,11 +1,11 @@ using FluentValidation; using FluentValidation.Results; -using Vegasco.WebApi.Authentication; -using Vegasco.WebApi.Common; -using Vegasco.WebApi.Persistence; -using Vegasco.WebApi.Users; +using Vegasco.Server.Api.Authentication; +using Vegasco.Server.Api.Common; +using Vegasco.Server.Api.Persistence; +using Vegasco.Server.Api.Users; -namespace Vegasco.WebApi.Cars; +namespace Vegasco.Server.Api.Cars; public static class CreateCar { diff --git a/src/WebApi/Cars/DeleteCar.cs b/src/Vegasco.Server.Api/Cars/DeleteCar.cs similarity index 89% rename from src/WebApi/Cars/DeleteCar.cs rename to src/Vegasco.Server.Api/Cars/DeleteCar.cs index 638ed4b..c78eff0 100644 --- a/src/WebApi/Cars/DeleteCar.cs +++ b/src/Vegasco.Server.Api/Cars/DeleteCar.cs @@ -1,6 +1,6 @@ -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Persistence; -namespace Vegasco.WebApi.Cars; +namespace Vegasco.Server.Api.Cars; public static class DeleteCar { diff --git a/src/WebApi/Cars/GetCar.cs b/src/Vegasco.Server.Api/Cars/GetCar.cs similarity index 89% rename from src/WebApi/Cars/GetCar.cs rename to src/Vegasco.Server.Api/Cars/GetCar.cs index beb8432..9fd6955 100644 --- a/src/WebApi/Cars/GetCar.cs +++ b/src/Vegasco.Server.Api/Cars/GetCar.cs @@ -1,6 +1,6 @@ -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Persistence; -namespace Vegasco.WebApi.Cars; +namespace Vegasco.Server.Api.Cars; public static class GetCar { diff --git a/src/WebApi/Cars/GetCars.cs b/src/Vegasco.Server.Api/Cars/GetCars.cs similarity index 93% rename from src/WebApi/Cars/GetCars.cs rename to src/Vegasco.Server.Api/Cars/GetCars.cs index 14d0c5e..cb4ce6e 100644 --- a/src/WebApi/Cars/GetCars.cs +++ b/src/Vegasco.Server.Api/Cars/GetCars.cs @@ -1,9 +1,9 @@ using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Persistence; -namespace Vegasco.WebApi.Cars; +namespace Vegasco.Server.Api.Cars; public static class GetCars { diff --git a/src/WebApi/Cars/UpdateCar.cs b/src/Vegasco.Server.Api/Cars/UpdateCar.cs similarity index 90% rename from src/WebApi/Cars/UpdateCar.cs rename to src/Vegasco.Server.Api/Cars/UpdateCar.cs index 38b27a9..2e06c52 100644 --- a/src/WebApi/Cars/UpdateCar.cs +++ b/src/Vegasco.Server.Api/Cars/UpdateCar.cs @@ -1,10 +1,10 @@ using FluentValidation; using FluentValidation.Results; -using Vegasco.WebApi.Authentication; -using Vegasco.WebApi.Common; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Authentication; +using Vegasco.Server.Api.Common; +using Vegasco.Server.Api.Persistence; -namespace Vegasco.WebApi.Cars; +namespace Vegasco.Server.Api.Cars; public static class UpdateCar { diff --git a/src/WebApi/Common/Constants.cs b/src/Vegasco.Server.Api/Common/Constants.cs similarity index 79% rename from src/WebApi/Common/Constants.cs rename to src/Vegasco.Server.Api/Common/Constants.cs index c160ccc..5e4b521 100644 --- a/src/WebApi/Common/Constants.cs +++ b/src/Vegasco.Server.Api/Common/Constants.cs @@ -1,4 +1,4 @@ -namespace Vegasco.WebApi.Common; +namespace Vegasco.Server.Api.Common; public static class Constants { diff --git a/src/WebApi/Common/DependencyInjectionExtensions.cs b/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs similarity index 88% rename from src/WebApi/Common/DependencyInjectionExtensions.cs rename to src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs index 7bd4fdf..041b3dd 100644 --- a/src/WebApi/Common/DependencyInjectionExtensions.cs +++ b/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs @@ -2,18 +2,19 @@ using FluentValidation; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Options; -using Vegasco.WebApi.Authentication; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Authentication; +using Vegasco.Server.Api.Common; +using Vegasco.Server.Api.Persistence; -namespace Vegasco.WebApi.Common; +namespace Vegasco.Server.Api.Common; public static class DependencyInjectionExtensions { /// - /// Adds all the WebApi related services to the Dependency Injection container. + /// Adds all the Api related services to the Dependency Injection container. /// /// - public static void AddWebApiServices(this IHostApplicationBuilder builder) + public static void AddApiServices(this IHostApplicationBuilder builder) { builder.Services .AddMiscellaneousServices() @@ -30,7 +31,7 @@ public static class DependencyInjectionExtensions services.AddValidatorsFromAssemblies( [ - typeof(IWebApiMarker).Assembly + typeof(IApiMarker).Assembly ], ServiceLifetime.Singleton); services.AddHealthChecks(); @@ -125,7 +126,7 @@ public static class DependencyInjectionExtensions private static IHostApplicationBuilder AddDbContext(this IHostApplicationBuilder builder) { - builder.AddNpgsqlDbContext(Server.AppHost.Shared.Constants.Database.Name); + builder.AddNpgsqlDbContext(AppHost.Shared.Constants.Database.Name); return builder; } } diff --git a/src/WebApi/Common/FluentValidationOptions.cs b/src/Vegasco.Server.Api/Common/FluentValidationOptions.cs similarity index 96% rename from src/WebApi/Common/FluentValidationOptions.cs rename to src/Vegasco.Server.Api/Common/FluentValidationOptions.cs index 3131736..eaa3164 100644 --- a/src/WebApi/Common/FluentValidationOptions.cs +++ b/src/Vegasco.Server.Api/Common/FluentValidationOptions.cs @@ -2,7 +2,7 @@ using FluentValidation.Results; using Microsoft.Extensions.Options; -namespace Vegasco.WebApi.Common; +namespace Vegasco.Server.Api.Common; public class FluentValidationOptions : IValidateOptions where TOptions : class diff --git a/src/Vegasco.Server.Api/Common/IApiMarker.cs b/src/Vegasco.Server.Api/Common/IApiMarker.cs new file mode 100644 index 0000000..643d2b3 --- /dev/null +++ b/src/Vegasco.Server.Api/Common/IApiMarker.cs @@ -0,0 +1,3 @@ +namespace Vegasco.Server.Api.Common; + +public interface IApiMarker; diff --git a/src/WebApi/Common/StartupExtensions.cs b/src/Vegasco.Server.Api/Common/StartupExtensions.cs similarity index 87% rename from src/WebApi/Common/StartupExtensions.cs rename to src/Vegasco.Server.Api/Common/StartupExtensions.cs index 54f077b..be0f48e 100644 --- a/src/WebApi/Common/StartupExtensions.cs +++ b/src/Vegasco.Server.Api/Common/StartupExtensions.cs @@ -1,8 +1,9 @@ using Microsoft.AspNetCore.Localization; using System.Globalization; -using Vegasco.WebApi.Endpoints; +using Vegasco.Server.Api.Endpoints; +using Vegasco.Server.ServiceDefaults; -namespace Vegasco.WebApi.Common; +namespace Vegasco.Server.Api.Common; internal static class StartupExtensions { @@ -12,7 +13,7 @@ internal static class StartupExtensions builder.Configuration.AddEnvironmentVariables("Vegasco_"); - builder.AddWebApiServices(); + builder.AddApiServices(); WebApplication app = builder.Build(); return app; diff --git a/src/WebApi/Common/ValidatorExtensions.cs b/src/Vegasco.Server.Api/Common/ValidatorExtensions.cs similarity index 96% rename from src/WebApi/Common/ValidatorExtensions.cs rename to src/Vegasco.Server.Api/Common/ValidatorExtensions.cs index f574e18..b93bb68 100644 --- a/src/WebApi/Common/ValidatorExtensions.cs +++ b/src/Vegasco.Server.Api/Common/ValidatorExtensions.cs @@ -2,7 +2,7 @@ using FluentValidation.Results; using Microsoft.Extensions.Options; -namespace Vegasco.WebApi.Common; +namespace Vegasco.Server.Api.Common; public static class ValidatorExtensions { @@ -38,7 +38,7 @@ public static class ValidatorExtensions { if (!combinedErrors.TryGetValue(error.PropertyName, out HashSet? value)) { - value = ([error.ErrorMessage]); + value = [error.ErrorMessage]; combinedErrors[error.PropertyName] = value; continue; } diff --git a/src/WebApi/Consumptions/Consumption.cs b/src/Vegasco.Server.Api/Consumptions/Consumption.cs similarity index 93% rename from src/WebApi/Consumptions/Consumption.cs rename to src/Vegasco.Server.Api/Consumptions/Consumption.cs index 3476e31..869b6af 100644 --- a/src/WebApi/Consumptions/Consumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/Consumption.cs @@ -1,8 +1,8 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -using Vegasco.WebApi.Cars; +using Vegasco.Server.Api.Cars; -namespace Vegasco.WebApi.Consumptions; +namespace Vegasco.Server.Api.Consumptions; public class Consumption { diff --git a/src/WebApi/Consumptions/ConsumptionId.cs b/src/Vegasco.Server.Api/Consumptions/ConsumptionId.cs similarity index 66% rename from src/WebApi/Consumptions/ConsumptionId.cs rename to src/Vegasco.Server.Api/Consumptions/ConsumptionId.cs index 5494b4a..e4936fb 100644 --- a/src/WebApi/Consumptions/ConsumptionId.cs +++ b/src/Vegasco.Server.Api/Consumptions/ConsumptionId.cs @@ -1,6 +1,6 @@ using StronglyTypedIds; -namespace Vegasco.WebApi.Consumptions; +namespace Vegasco.Server.Api.Consumptions; [StronglyTypedId] diff --git a/src/WebApi/Consumptions/CreateConsumption.cs b/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs similarity index 93% rename from src/WebApi/Consumptions/CreateConsumption.cs rename to src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs index 7d5def3..b890632 100644 --- a/src/WebApi/Consumptions/CreateConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs @@ -1,10 +1,10 @@ using FluentValidation; using FluentValidation.Results; -using Vegasco.WebApi.Cars; -using Vegasco.WebApi.Common; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Cars; +using Vegasco.Server.Api.Common; +using Vegasco.Server.Api.Persistence; -namespace Vegasco.WebApi.Consumptions; +namespace Vegasco.Server.Api.Consumptions; public static class CreateConsumption { diff --git a/src/WebApi/Consumptions/DeleteConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs similarity index 89% rename from src/WebApi/Consumptions/DeleteConsumptions.cs rename to src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs index aa93da5..8b6f4e9 100644 --- a/src/WebApi/Consumptions/DeleteConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs @@ -1,6 +1,6 @@ -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Persistence; -namespace Vegasco.WebApi.Consumptions; +namespace Vegasco.Server.Api.Consumptions; public static class DeleteConsumption { diff --git a/src/WebApi/Consumptions/GetConsumption.cs b/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs similarity index 91% rename from src/WebApi/Consumptions/GetConsumption.cs rename to src/Vegasco.Server.Api/Consumptions/GetConsumption.cs index bcb9882..2f29fc8 100644 --- a/src/WebApi/Consumptions/GetConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs @@ -1,6 +1,6 @@ -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Persistence; -namespace Vegasco.WebApi.Consumptions; +namespace Vegasco.Server.Api.Consumptions; public static class GetConsumption { diff --git a/src/WebApi/Consumptions/GetConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs similarity index 94% rename from src/WebApi/Consumptions/GetConsumptions.cs rename to src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs index 9168366..84fcebd 100644 --- a/src/WebApi/Consumptions/GetConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs @@ -1,9 +1,9 @@ using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Persistence; -namespace Vegasco.WebApi.Consumptions; +namespace Vegasco.Server.Api.Consumptions; public static class GetConsumptions { diff --git a/src/WebApi/Consumptions/UpdateConsumption.cs b/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs similarity index 94% rename from src/WebApi/Consumptions/UpdateConsumption.cs rename to src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs index 8d27bad..868946a 100644 --- a/src/WebApi/Consumptions/UpdateConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs @@ -1,9 +1,9 @@ using FluentValidation; using FluentValidation.Results; -using Vegasco.WebApi.Common; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Common; +using Vegasco.Server.Api.Persistence; -namespace Vegasco.WebApi.Consumptions; +namespace Vegasco.Server.Api.Consumptions; public static class UpdateConsumption { diff --git a/src/WebApi/Endpoints/EndpointExtensions.cs b/src/Vegasco.Server.Api/Endpoints/EndpointExtensions.cs similarity index 81% rename from src/WebApi/Endpoints/EndpointExtensions.cs rename to src/Vegasco.Server.Api/Endpoints/EndpointExtensions.cs index 599ff6d..c389ef7 100644 --- a/src/WebApi/Endpoints/EndpointExtensions.cs +++ b/src/Vegasco.Server.Api/Endpoints/EndpointExtensions.cs @@ -1,12 +1,11 @@ using Asp.Versioning.Builder; using Asp.Versioning.Conventions; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Vegasco.WebApi.Cars; -using Vegasco.WebApi.Common; -using Vegasco.WebApi.Consumptions; -using Vegasco.WebApi.Info; +using Vegasco.Server.Api.Cars; +using Vegasco.Server.Api.Common; +using Vegasco.Server.Api.Consumptions; +using Vegasco.Server.Api.Info; -namespace Vegasco.WebApi.Endpoints; +namespace Vegasco.Server.Api.Endpoints; public static class EndpointExtensions { diff --git a/src/WebApi/Info/GetServerInfo.cs b/src/Vegasco.Server.Api/Info/GetServerInfo.cs similarity index 95% rename from src/WebApi/Info/GetServerInfo.cs rename to src/Vegasco.Server.Api/Info/GetServerInfo.cs index f63cd73..730b83b 100644 --- a/src/WebApi/Info/GetServerInfo.cs +++ b/src/Vegasco.Server.Api/Info/GetServerInfo.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Http.HttpResults; -namespace Vegasco.WebApi.Info; +namespace Vegasco.Server.Api.Info; public class GetServerInfo { diff --git a/src/WebApi/Persistence/ApplicationDbContext.cs b/src/Vegasco.Server.Api/Persistence/ApplicationDbContext.cs similarity index 60% rename from src/WebApi/Persistence/ApplicationDbContext.cs rename to src/Vegasco.Server.Api/Persistence/ApplicationDbContext.cs index 6c436c9..1b35885 100644 --- a/src/WebApi/Persistence/ApplicationDbContext.cs +++ b/src/Vegasco.Server.Api/Persistence/ApplicationDbContext.cs @@ -1,10 +1,10 @@ using Microsoft.EntityFrameworkCore; -using Vegasco.WebApi.Cars; -using Vegasco.WebApi.Common; -using Vegasco.WebApi.Consumptions; -using Vegasco.WebApi.Users; +using Vegasco.Server.Api.Cars; +using Vegasco.Server.Api.Common; +using Vegasco.Server.Api.Consumptions; +using Vegasco.Server.Api.Users; -namespace Vegasco.WebApi.Persistence; +namespace Vegasco.Server.Api.Persistence; public class ApplicationDbContext(DbContextOptions options) : DbContext(options) { @@ -17,6 +17,6 @@ public class ApplicationDbContext(DbContextOptions options protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); - modelBuilder.ApplyConfigurationsFromAssembly(typeof(IWebApiMarker).Assembly); + modelBuilder.ApplyConfigurationsFromAssembly(typeof(IApiMarker).Assembly); } } diff --git a/src/WebApi/Persistence/ApplyMigrationsService.cs b/src/Vegasco.Server.Api/Persistence/ApplyMigrationsService.cs similarity index 93% rename from src/WebApi/Persistence/ApplyMigrationsService.cs rename to src/Vegasco.Server.Api/Persistence/ApplyMigrationsService.cs index c135ed1..19b9d70 100644 --- a/src/WebApi/Persistence/ApplyMigrationsService.cs +++ b/src/Vegasco.Server.Api/Persistence/ApplyMigrationsService.cs @@ -1,6 +1,6 @@ using Microsoft.EntityFrameworkCore; -namespace Vegasco.WebApi.Persistence; +namespace Vegasco.Server.Api.Persistence; public class ApplyMigrationsService(ILogger logger, IServiceScopeFactory scopeFactory) : IHostedService diff --git a/src/WebApi/Persistence/Migrations/20240818105918_Initial.Designer.cs b/src/Vegasco.Server.Api/Persistence/Migrations/20240818105918_Initial.Designer.cs similarity index 81% rename from src/WebApi/Persistence/Migrations/20240818105918_Initial.Designer.cs rename to src/Vegasco.Server.Api/Persistence/Migrations/20240818105918_Initial.Designer.cs index 046955c..9e48148 100644 --- a/src/WebApi/Persistence/Migrations/20240818105918_Initial.Designer.cs +++ b/src/Vegasco.Server.Api/Persistence/Migrations/20240818105918_Initial.Designer.cs @@ -5,11 +5,12 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Persistence; + #nullable disable -namespace Vegasco.WebApi.Persistence.Migrations +namespace Vegasco.Server.Api.Persistence.Migrations { [DbContext(typeof(ApplicationDbContext))] [Migration("20240818105918_Initial")] @@ -25,7 +26,7 @@ namespace Vegasco.WebApi.Persistence.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => + modelBuilder.Entity("Vegasco.Server.Api.Cars.Car", b => { b.Property("Id") .HasColumnType("uuid"); @@ -46,7 +47,7 @@ namespace Vegasco.WebApi.Persistence.Migrations b.ToTable("Cars"); }); - modelBuilder.Entity("Vegasco.WebApi.Consumptions.Consumption", b => + modelBuilder.Entity("Vegasco.Server.Api.Consumptions.Consumption", b => { b.Property("Id") .HasColumnType("uuid"); @@ -73,7 +74,7 @@ namespace Vegasco.WebApi.Persistence.Migrations b.ToTable("Consumptions"); }); - modelBuilder.Entity("Vegasco.WebApi.Users.User", b => + modelBuilder.Entity("Vegasco.Server.Api.Users.User", b => { b.Property("Id") .HasColumnType("text"); @@ -83,9 +84,9 @@ namespace Vegasco.WebApi.Persistence.Migrations b.ToTable("Users"); }); - modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => + modelBuilder.Entity("Vegasco.Server.Api.Cars.Car", b => { - b.HasOne("Vegasco.WebApi.Users.User", "User") + b.HasOne("Vegasco.Server.Api.Users.User", "User") .WithMany("Cars") .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -94,9 +95,9 @@ namespace Vegasco.WebApi.Persistence.Migrations b.Navigation("User"); }); - modelBuilder.Entity("Vegasco.WebApi.Consumptions.Consumption", b => + modelBuilder.Entity("Vegasco.Server.Api.Consumptions.Consumption", b => { - b.HasOne("Vegasco.WebApi.Cars.Car", "Car") + b.HasOne("Vegasco.Server.Api.Cars.Car", "Car") .WithMany("Consumptions") .HasForeignKey("CarId") .OnDelete(DeleteBehavior.Cascade) @@ -105,12 +106,12 @@ namespace Vegasco.WebApi.Persistence.Migrations b.Navigation("Car"); }); - modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => + modelBuilder.Entity("Vegasco.Server.Api.Cars.Car", b => { b.Navigation("Consumptions"); }); - modelBuilder.Entity("Vegasco.WebApi.Users.User", b => + modelBuilder.Entity("Vegasco.Server.Api.Users.User", b => { b.Navigation("Cars"); }); diff --git a/src/WebApi/Persistence/Migrations/20240818105918_Initial.cs b/src/Vegasco.Server.Api/Persistence/Migrations/20240818105918_Initial.cs similarity index 96% rename from src/WebApi/Persistence/Migrations/20240818105918_Initial.cs rename to src/Vegasco.Server.Api/Persistence/Migrations/20240818105918_Initial.cs index be2f349..90e5b37 100644 --- a/src/WebApi/Persistence/Migrations/20240818105918_Initial.cs +++ b/src/Vegasco.Server.Api/Persistence/Migrations/20240818105918_Initial.cs @@ -1,9 +1,8 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Migrations; #nullable disable -namespace Vegasco.WebApi.Persistence.Migrations +namespace Vegasco.Server.Api.Persistence.Migrations { /// public partial class Initial : Migration diff --git a/src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Vegasco.Server.Api/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs similarity index 80% rename from src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs rename to src/Vegasco.Server.Api/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index 390dc72..c29dadd 100644 --- a/src/WebApi/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Vegasco.Server.Api/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -4,11 +4,12 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Persistence; + #nullable disable -namespace Vegasco.WebApi.Persistence.Migrations +namespace Vegasco.Server.Api.Persistence.Migrations { [DbContext(typeof(ApplicationDbContext))] partial class ApplicationDbContextModelSnapshot : ModelSnapshot @@ -22,7 +23,7 @@ namespace Vegasco.WebApi.Persistence.Migrations NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => + modelBuilder.Entity("Vegasco.Server.Api.Cars.Car", b => { b.Property("Id") .HasColumnType("uuid"); @@ -43,7 +44,7 @@ namespace Vegasco.WebApi.Persistence.Migrations b.ToTable("Cars"); }); - modelBuilder.Entity("Vegasco.WebApi.Consumptions.Consumption", b => + modelBuilder.Entity("Vegasco.Server.Api.Consumptions.Consumption", b => { b.Property("Id") .HasColumnType("uuid"); @@ -70,7 +71,7 @@ namespace Vegasco.WebApi.Persistence.Migrations b.ToTable("Consumptions"); }); - modelBuilder.Entity("Vegasco.WebApi.Users.User", b => + modelBuilder.Entity("Vegasco.Server.Api.Users.User", b => { b.Property("Id") .HasColumnType("text"); @@ -80,9 +81,9 @@ namespace Vegasco.WebApi.Persistence.Migrations b.ToTable("Users"); }); - modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => + modelBuilder.Entity("Vegasco.Server.Api.Cars.Car", b => { - b.HasOne("Vegasco.WebApi.Users.User", "User") + b.HasOne("Vegasco.Server.Api.Users.User", "User") .WithMany("Cars") .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) @@ -91,9 +92,9 @@ namespace Vegasco.WebApi.Persistence.Migrations b.Navigation("User"); }); - modelBuilder.Entity("Vegasco.WebApi.Consumptions.Consumption", b => + modelBuilder.Entity("Vegasco.Server.Api.Consumptions.Consumption", b => { - b.HasOne("Vegasco.WebApi.Cars.Car", "Car") + b.HasOne("Vegasco.Server.Api.Cars.Car", "Car") .WithMany("Consumptions") .HasForeignKey("CarId") .OnDelete(DeleteBehavior.Cascade) @@ -102,12 +103,12 @@ namespace Vegasco.WebApi.Persistence.Migrations b.Navigation("Car"); }); - modelBuilder.Entity("Vegasco.WebApi.Cars.Car", b => + modelBuilder.Entity("Vegasco.Server.Api.Cars.Car", b => { b.Navigation("Consumptions"); }); - modelBuilder.Entity("Vegasco.WebApi.Users.User", b => + modelBuilder.Entity("Vegasco.Server.Api.Users.User", b => { b.Navigation("Cars"); }); diff --git a/src/WebApi/Program.cs b/src/Vegasco.Server.Api/Program.cs similarity index 74% rename from src/WebApi/Program.cs rename to src/Vegasco.Server.Api/Program.cs index fd643eb..6a2b441 100644 --- a/src/WebApi/Program.cs +++ b/src/Vegasco.Server.Api/Program.cs @@ -1,4 +1,4 @@ -using Vegasco.WebApi.Common; +using Vegasco.Server.Api.Common; WebApplication.CreateBuilder(args) .ConfigureServices() diff --git a/src/WebApi/Properties/launchSettings.json b/src/Vegasco.Server.Api/Properties/launchSettings.json similarity index 100% rename from src/WebApi/Properties/launchSettings.json rename to src/Vegasco.Server.Api/Properties/launchSettings.json diff --git a/src/WebApi/Users/User.cs b/src/Vegasco.Server.Api/Users/User.cs similarity index 62% rename from src/WebApi/Users/User.cs rename to src/Vegasco.Server.Api/Users/User.cs index cc5a46d..e114b4e 100644 --- a/src/WebApi/Users/User.cs +++ b/src/Vegasco.Server.Api/Users/User.cs @@ -1,6 +1,6 @@ -using Vegasco.WebApi.Cars; +using Vegasco.Server.Api.Cars; -namespace Vegasco.WebApi.Users; +namespace Vegasco.Server.Api.Users; public class User { diff --git a/src/WebApi/Users/UserTableConfiguration.cs b/src/Vegasco.Server.Api/Users/UserTableConfiguration.cs similarity index 88% rename from src/WebApi/Users/UserTableConfiguration.cs rename to src/Vegasco.Server.Api/Users/UserTableConfiguration.cs index cf5a29a..6abe0ee 100644 --- a/src/WebApi/Users/UserTableConfiguration.cs +++ b/src/Vegasco.Server.Api/Users/UserTableConfiguration.cs @@ -1,7 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; -namespace Vegasco.WebApi.Users; +namespace Vegasco.Server.Api.Users; public class UserTableConfiguration : IEntityTypeConfiguration { diff --git a/src/WebApi/WebApi.csproj b/src/Vegasco.Server.Api/Vegasco.Server.Api.csproj similarity index 97% rename from src/WebApi/WebApi.csproj rename to src/Vegasco.Server.Api/Vegasco.Server.Api.csproj index 9ba8886..f09473e 100644 --- a/src/WebApi/WebApi.csproj +++ b/src/Vegasco.Server.Api/Vegasco.Server.Api.csproj @@ -7,7 +7,7 @@ 4bf893d3-0c16-41ec-8b46-2768d841215d Linux ..\.. - Vegasco.WebApi + Vegasco.Server.Api diff --git a/src/WebApi/appsettings.Development.json b/src/Vegasco.Server.Api/appsettings.Development.json similarity index 100% rename from src/WebApi/appsettings.Development.json rename to src/Vegasco.Server.Api/appsettings.Development.json diff --git a/src/WebApi/appsettings.json b/src/Vegasco.Server.Api/appsettings.json similarity index 100% rename from src/WebApi/appsettings.json rename to src/Vegasco.Server.Api/appsettings.json diff --git a/src/Vegasco.Server.AppHost.Shared/Constants.cs b/src/Vegasco.Server.AppHost.Shared/Constants.cs index cdd67eb..9c1dc4c 100644 --- a/src/Vegasco.Server.AppHost.Shared/Constants.cs +++ b/src/Vegasco.Server.AppHost.Shared/Constants.cs @@ -4,7 +4,7 @@ public static class Constants { public static class Projects { - public const string WebApiName = "webapi"; + public const string Api = "Vegasco_Server_Api"; } public static class Database diff --git a/src/Vegasco.Server.AppHost/Program.cs b/src/Vegasco.Server.AppHost/Program.cs index 6c19fd7..476f8c4 100644 --- a/src/Vegasco.Server.AppHost/Program.cs +++ b/src/Vegasco.Server.AppHost/Program.cs @@ -7,7 +7,7 @@ IResourceBuilder postgres = builder.AddPostgres(Consta .AddDatabase(Constants.Database.Name); builder - .AddProject(Constants.Projects.WebApiName) + .AddProject(Constants.Projects.Api) .WithReference(postgres) .WaitFor(postgres); diff --git a/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj b/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj index 70315a2..e4de3b0 100644 --- a/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj +++ b/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/Vegasco.Server.ServiceDefaults/Extensions.cs b/src/Vegasco.Server.ServiceDefaults/Extensions.cs index 5984246..82fd99b 100644 --- a/src/Vegasco.Server.ServiceDefaults/Extensions.cs +++ b/src/Vegasco.Server.ServiceDefaults/Extensions.cs @@ -2,13 +2,13 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.ServiceDiscovery; using OpenTelemetry; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; -namespace Microsoft.Extensions.Hosting; +namespace Vegasco.Server.ServiceDefaults; // Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. // This project should be referenced by each service project in your solution. diff --git a/src/WebApi/Common/IWebApiMarker.cs b/src/WebApi/Common/IWebApiMarker.cs deleted file mode 100644 index bcd335d..0000000 --- a/src/WebApi/Common/IWebApiMarker.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Vegasco.WebApi.Common; - -public interface IWebApiMarker; diff --git a/tests/WebApi.Tests.Integration/CarFaker.cs b/tests/Vegasco.Server.Api.Tests.Integration/CarFaker.cs similarity index 79% rename from tests/WebApi.Tests.Integration/CarFaker.cs rename to tests/Vegasco.Server.Api.Tests.Integration/CarFaker.cs index 7364709..c8533d6 100644 --- a/tests/WebApi.Tests.Integration/CarFaker.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/CarFaker.cs @@ -1,7 +1,7 @@ using Bogus; -using Vegasco.WebApi.Cars; +using Vegasco.Server.Api.Cars; -namespace WebApi.Tests.Integration; +namespace Vegasco.Server.Api.Tests.Integration; internal class CarFaker { diff --git a/tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Cars/CreateCarTests.cs similarity index 94% rename from tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs rename to tests/Vegasco.Server.Api.Tests.Integration/Cars/CreateCarTests.cs index 95d2c03..28d3dd5 100644 --- a/tests/WebApi.Tests.Integration/Cars/CreateCarTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Cars/CreateCarTests.cs @@ -3,10 +3,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using System.Net; using System.Net.Http.Json; -using Vegasco.WebApi.Cars; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Cars; +using Vegasco.Server.Api.Persistence; -namespace WebApi.Tests.Integration.Cars; +namespace Vegasco.Server.Api.Tests.Integration.Cars; [Collection(SharedTestCollection.Name)] public class CreateCarTests : IAsyncLifetime diff --git a/tests/WebApi.Tests.Integration/Cars/DeleteCarTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Cars/DeleteCarTests.cs similarity index 93% rename from tests/WebApi.Tests.Integration/Cars/DeleteCarTests.cs rename to tests/Vegasco.Server.Api.Tests.Integration/Cars/DeleteCarTests.cs index 160d96e..ba76a29 100644 --- a/tests/WebApi.Tests.Integration/Cars/DeleteCarTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Cars/DeleteCarTests.cs @@ -2,10 +2,10 @@ using Microsoft.Extensions.DependencyInjection; using System.Net; using System.Net.Http.Json; -using Vegasco.WebApi.Cars; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Cars; +using Vegasco.Server.Api.Persistence; -namespace WebApi.Tests.Integration.Cars; +namespace Vegasco.Server.Api.Tests.Integration.Cars; [Collection(SharedTestCollection.Name)] public class DeleteCarTests : IAsyncLifetime diff --git a/tests/WebApi.Tests.Integration/Cars/GetCarTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarTests.cs similarity index 94% rename from tests/WebApi.Tests.Integration/Cars/GetCarTests.cs rename to tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarTests.cs index f425919..81ddb53 100644 --- a/tests/WebApi.Tests.Integration/Cars/GetCarTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarTests.cs @@ -1,9 +1,9 @@ using FluentAssertions; using System.Net; using System.Net.Http.Json; -using Vegasco.WebApi.Cars; +using Vegasco.Server.Api.Cars; -namespace WebApi.Tests.Integration.Cars; +namespace Vegasco.Server.Api.Tests.Integration.Cars; [Collection(SharedTestCollection.Name)] public class GetCarTests : IAsyncLifetime diff --git a/tests/WebApi.Tests.Integration/Cars/GetCarsTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarsTests.cs similarity index 95% rename from tests/WebApi.Tests.Integration/Cars/GetCarsTests.cs rename to tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarsTests.cs index 5924713..17a45dd 100644 --- a/tests/WebApi.Tests.Integration/Cars/GetCarsTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarsTests.cs @@ -1,9 +1,9 @@ using FluentAssertions; using System.Net; using System.Net.Http.Json; -using Vegasco.WebApi.Cars; +using Vegasco.Server.Api.Cars; -namespace WebApi.Tests.Integration.Cars; +namespace Vegasco.Server.Api.Tests.Integration.Cars; [Collection(SharedTestCollection.Name)] public class GetCarsTests : IAsyncLifetime diff --git a/tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Cars/UpdateCarTests.cs similarity index 96% rename from tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs rename to tests/Vegasco.Server.Api.Tests.Integration/Cars/UpdateCarTests.cs index ded5967..3896669 100644 --- a/tests/WebApi.Tests.Integration/Cars/UpdateCarTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Cars/UpdateCarTests.cs @@ -3,10 +3,10 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using System.Net; using System.Net.Http.Json; -using Vegasco.WebApi.Cars; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Cars; +using Vegasco.Server.Api.Persistence; -namespace WebApi.Tests.Integration.Cars; +namespace Vegasco.Server.Api.Tests.Integration.Cars; [Collection(SharedTestCollection.Name)] public class UpdateCarTests : IAsyncLifetime diff --git a/tests/WebApi.Tests.Integration/ConsumptionFaker.cs b/tests/Vegasco.Server.Api.Tests.Integration/ConsumptionFaker.cs similarity index 88% rename from tests/WebApi.Tests.Integration/ConsumptionFaker.cs rename to tests/Vegasco.Server.Api.Tests.Integration/ConsumptionFaker.cs index d4e84a3..681ea4a 100644 --- a/tests/WebApi.Tests.Integration/ConsumptionFaker.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/ConsumptionFaker.cs @@ -1,7 +1,7 @@ using Bogus; -using Vegasco.WebApi.Consumptions; +using Vegasco.Server.Api.Consumptions; -namespace WebApi.Tests.Integration; +namespace Vegasco.Server.Api.Tests.Integration; internal class ConsumptionFaker { diff --git a/tests/WebApi.Tests.Integration/Consumptions/CreateConsumptionTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/CreateConsumptionTests.cs similarity index 95% rename from tests/WebApi.Tests.Integration/Consumptions/CreateConsumptionTests.cs rename to tests/Vegasco.Server.Api.Tests.Integration/Consumptions/CreateConsumptionTests.cs index 30887e9..123b24b 100644 --- a/tests/WebApi.Tests.Integration/Consumptions/CreateConsumptionTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/CreateConsumptionTests.cs @@ -3,11 +3,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using System.Net; using System.Net.Http.Json; -using Vegasco.WebApi.Cars; -using Vegasco.WebApi.Consumptions; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Cars; +using Vegasco.Server.Api.Consumptions; +using Vegasco.Server.Api.Persistence; -namespace WebApi.Tests.Integration.Consumptions; +namespace Vegasco.Server.Api.Tests.Integration.Consumptions; [Collection(SharedTestCollection.Name)] public class CreateConsumptionTests : IAsyncLifetime diff --git a/tests/WebApi.Tests.Integration/Consumptions/DeleteConsumptionTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/DeleteConsumptionTests.cs similarity index 94% rename from tests/WebApi.Tests.Integration/Consumptions/DeleteConsumptionTests.cs rename to tests/Vegasco.Server.Api.Tests.Integration/Consumptions/DeleteConsumptionTests.cs index af72f99..113109a 100644 --- a/tests/WebApi.Tests.Integration/Consumptions/DeleteConsumptionTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/DeleteConsumptionTests.cs @@ -2,11 +2,11 @@ using Microsoft.Extensions.DependencyInjection; using System.Net; using System.Net.Http.Json; -using Vegasco.WebApi.Cars; -using Vegasco.WebApi.Consumptions; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Cars; +using Vegasco.Server.Api.Consumptions; +using Vegasco.Server.Api.Persistence; -namespace WebApi.Tests.Integration.Consumptions; +namespace Vegasco.Server.Api.Tests.Integration.Consumptions; [Collection(SharedTestCollection.Name)] public class DeleteConsumptionTests : IAsyncLifetime diff --git a/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionTests.cs similarity index 94% rename from tests/WebApi.Tests.Integration/Consumptions/GetConsumptionTests.cs rename to tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionTests.cs index 6448776..8596d73 100644 --- a/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionTests.cs @@ -2,11 +2,11 @@ using Microsoft.Extensions.DependencyInjection; using System.Net; using System.Net.Http.Json; -using Vegasco.WebApi.Cars; -using Vegasco.WebApi.Consumptions; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Cars; +using Vegasco.Server.Api.Consumptions; +using Vegasco.Server.Api.Persistence; -namespace WebApi.Tests.Integration.Consumptions; +namespace Vegasco.Server.Api.Tests.Integration.Consumptions; [Collection(SharedTestCollection.Name)] public class GetConsumptionTests : IAsyncLifetime diff --git a/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionsTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionsTests.cs similarity index 94% rename from tests/WebApi.Tests.Integration/Consumptions/GetConsumptionsTests.cs rename to tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionsTests.cs index dc919ee..f93ee0a 100644 --- a/tests/WebApi.Tests.Integration/Consumptions/GetConsumptionsTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionsTests.cs @@ -2,11 +2,11 @@ using Microsoft.Extensions.DependencyInjection; using System.Net; using System.Net.Http.Json; -using Vegasco.WebApi.Cars; -using Vegasco.WebApi.Consumptions; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Cars; +using Vegasco.Server.Api.Consumptions; +using Vegasco.Server.Api.Persistence; -namespace WebApi.Tests.Integration.Consumptions; +namespace Vegasco.Server.Api.Tests.Integration.Consumptions; [Collection(SharedTestCollection.Name)] public class GetConsumptionsTests : IAsyncLifetime diff --git a/tests/WebApi.Tests.Integration/Consumptions/UpdateConsumptionTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/UpdateConsumptionTests.cs similarity index 96% rename from tests/WebApi.Tests.Integration/Consumptions/UpdateConsumptionTests.cs rename to tests/Vegasco.Server.Api.Tests.Integration/Consumptions/UpdateConsumptionTests.cs index 3999b1d..9f0c1a6 100644 --- a/tests/WebApi.Tests.Integration/Consumptions/UpdateConsumptionTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/UpdateConsumptionTests.cs @@ -3,11 +3,11 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using System.Net; using System.Net.Http.Json; -using Vegasco.WebApi.Cars; -using Vegasco.WebApi.Consumptions; -using Vegasco.WebApi.Persistence; +using Vegasco.Server.Api.Cars; +using Vegasco.Server.Api.Consumptions; +using Vegasco.Server.Api.Persistence; -namespace WebApi.Tests.Integration.Consumptions; +namespace Vegasco.Server.Api.Tests.Integration.Consumptions; [Collection(SharedTestCollection.Name)] public class UpdateConsumptionTests : IAsyncLifetime diff --git a/tests/WebApi.Tests.Integration/FluentAssertionConfiguration.cs b/tests/Vegasco.Server.Api.Tests.Integration/FluentAssertionConfiguration.cs similarity index 93% rename from tests/WebApi.Tests.Integration/FluentAssertionConfiguration.cs rename to tests/Vegasco.Server.Api.Tests.Integration/FluentAssertionConfiguration.cs index 019e491..235018a 100644 --- a/tests/WebApi.Tests.Integration/FluentAssertionConfiguration.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/FluentAssertionConfiguration.cs @@ -1,6 +1,7 @@ using FluentAssertions; -namespace WebApi.Tests.Integration; +namespace Vegasco.Server.Api.Tests.Integration; + internal static class FluentAssertionConfiguration { private const int DateTimeComparisonPrecision = 100; diff --git a/tests/WebApi.Tests.Integration/Info/GetServerInfoTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Info/GetServerInfoTests.cs similarity index 92% rename from tests/WebApi.Tests.Integration/Info/GetServerInfoTests.cs rename to tests/Vegasco.Server.Api.Tests.Integration/Info/GetServerInfoTests.cs index 1ce6a14..2732bec 100644 --- a/tests/WebApi.Tests.Integration/Info/GetServerInfoTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Info/GetServerInfoTests.cs @@ -1,9 +1,9 @@ using System.Net.Http.Json; using FluentAssertions; using FluentAssertions.Extensions; -using Vegasco.WebApi.Info; +using Vegasco.Server.Api.Info; -namespace WebApi.Tests.Integration.Info; +namespace Vegasco.Server.Api.Tests.Integration.Info; [Collection(SharedTestCollection.Name)] public class GetServerInfoTests diff --git a/tests/WebApi.Tests.Integration/PostgresRespawner.cs b/tests/Vegasco.Server.Api.Tests.Integration/PostgresRespawner.cs similarity index 94% rename from tests/WebApi.Tests.Integration/PostgresRespawner.cs rename to tests/Vegasco.Server.Api.Tests.Integration/PostgresRespawner.cs index d969aac..f852765 100644 --- a/tests/WebApi.Tests.Integration/PostgresRespawner.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/PostgresRespawner.cs @@ -2,7 +2,7 @@ using Respawn; using System.Data.Common; -namespace WebApi.Tests.Integration; +namespace Vegasco.Server.Api.Tests.Integration; internal sealed class PostgresRespawner : IDisposable { private readonly DbConnection _connection; diff --git a/tests/WebApi.Tests.Integration/SharedTestCollection.cs b/tests/Vegasco.Server.Api.Tests.Integration/SharedTestCollection.cs similarity index 76% rename from tests/WebApi.Tests.Integration/SharedTestCollection.cs rename to tests/Vegasco.Server.Api.Tests.Integration/SharedTestCollection.cs index bb824b2..f8ebc6d 100644 --- a/tests/WebApi.Tests.Integration/SharedTestCollection.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/SharedTestCollection.cs @@ -1,4 +1,4 @@ -namespace WebApi.Tests.Integration; +namespace Vegasco.Server.Api.Tests.Integration; [CollectionDefinition(Name)] public class SharedTestCollection : ICollectionFixture diff --git a/tests/WebApi.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs b/tests/Vegasco.Server.Api.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs similarity index 96% rename from tests/WebApi.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs rename to tests/Vegasco.Server.Api.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs index 787d255..7c9deac 100644 --- a/tests/WebApi.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/TestUserAlwaysAuthorizedPolicyEvaluator.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Http; using System.Security.Claims; -namespace WebApi.Tests.Integration; +namespace Vegasco.Server.Api.Tests.Integration; public sealed class TestUserAlwaysAuthorizedPolicyEvaluator : IPolicyEvaluator { diff --git a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj b/tests/Vegasco.Server.Api.Tests.Integration/Vegasco.Server.Api.Tests.Integration.csproj similarity index 89% rename from tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj rename to tests/Vegasco.Server.Api.Tests.Integration/Vegasco.Server.Api.Tests.Integration.csproj index ebfdb83..8007836 100644 --- a/tests/WebApi.Tests.Integration/WebApi.Tests.Integration.csproj +++ b/tests/Vegasco.Server.Api.Tests.Integration/Vegasco.Server.Api.Tests.Integration.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -16,7 +16,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -32,7 +32,7 @@ - + diff --git a/tests/WebApi.Tests.Integration/WebAppFactory.cs b/tests/Vegasco.Server.Api.Tests.Integration/WebAppFactory.cs similarity index 86% rename from tests/WebApi.Tests.Integration/WebAppFactory.cs rename to tests/Vegasco.Server.Api.Tests.Integration/WebAppFactory.cs index 892518a..3264e46 100644 --- a/tests/WebApi.Tests.Integration/WebAppFactory.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/WebAppFactory.cs @@ -7,11 +7,11 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Testcontainers.PostgreSql; -using Vegasco.WebApi.Common; +using Vegasco.Server.Api.Common; -namespace WebApi.Tests.Integration; +namespace Vegasco.Server.Api.Tests.Integration; -public sealed class WebAppFactory : WebApplicationFactory, IAsyncLifetime +public sealed class WebAppFactory : WebApplicationFactory, IAsyncLifetime { private readonly PostgreSqlContainer _database = new PostgreSqlBuilder() .WithImage(DockerImage) @@ -38,7 +38,7 @@ public sealed class WebAppFactory : WebApplicationFactory, IAsync { IEnumerable> customConfig = [ - new KeyValuePair($"ConnectionStrings:{Vegasco.Server.AppHost.Shared.Constants.Database.Name}", _database.GetConnectionString()), + new KeyValuePair($"ConnectionStrings:{AppHost.Shared.Constants.Database.Name}", _database.GetConnectionString()), new KeyValuePair("JWT:ValidAudience", "https://localhost"), new KeyValuePair("JWT:MetadataUrl", "https://localhost"), new KeyValuePair("JWT:NameClaimType", null), diff --git a/tests/WebApi.Tests.Unit/Authentication/UserAccessorTests.cs b/tests/Vegasco.Server.Api.Tests.Unit/Authentication/UserAccessorTests.cs similarity index 97% rename from tests/WebApi.Tests.Unit/Authentication/UserAccessorTests.cs rename to tests/Vegasco.Server.Api.Tests.Unit/Authentication/UserAccessorTests.cs index 0e5a68c..138819f 100644 --- a/tests/WebApi.Tests.Unit/Authentication/UserAccessorTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Unit/Authentication/UserAccessorTests.cs @@ -3,9 +3,9 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using NSubstitute; using System.Security.Claims; -using Vegasco.WebApi.Authentication; +using Vegasco.Server.Api.Authentication; -namespace WebApi.Tests.Unit.Authentication; +namespace Vegasco.Server.Api.Tests.Unit.Authentication; public sealed class UserAccessorTests { private readonly UserAccessor _sut; diff --git a/tests/WebApi.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs b/tests/Vegasco.Server.Api.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs similarity index 95% rename from tests/WebApi.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs rename to tests/Vegasco.Server.Api.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs index 2185cd3..d65f8af 100644 --- a/tests/WebApi.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Unit/Cars/CreateCarRequestValidatorTests.cs @@ -1,8 +1,8 @@ using FluentAssertions; using FluentValidation.Results; -using Vegasco.WebApi.Cars; +using Vegasco.Server.Api.Cars; -namespace WebApi.Tests.Unit.Cars; +namespace Vegasco.Server.Api.Tests.Unit.Cars; public sealed class CreateCarRequestValidatorTests { diff --git a/tests/WebApi.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs b/tests/Vegasco.Server.Api.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs similarity index 95% rename from tests/WebApi.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs rename to tests/Vegasco.Server.Api.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs index 366fd5a..dfbdc03 100644 --- a/tests/WebApi.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Unit/Cars/UpdateCarRequestValidatorTests.cs @@ -1,8 +1,8 @@ using FluentAssertions; using FluentValidation.Results; -using Vegasco.WebApi.Cars; +using Vegasco.Server.Api.Cars; -namespace WebApi.Tests.Unit.Cars; +namespace Vegasco.Server.Api.Tests.Unit.Cars; public sealed class UpdateCarRequestValidatorTests { diff --git a/tests/WebApi.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs b/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs similarity index 96% rename from tests/WebApi.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs rename to tests/Vegasco.Server.Api.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs index fa7f346..2c7c3ab 100644 --- a/tests/WebApi.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs @@ -1,9 +1,9 @@ using FluentAssertions; using FluentValidation.Results; using NSubstitute; -using Vegasco.WebApi.Consumptions; +using Vegasco.Server.Api.Consumptions; -namespace WebApi.Tests.Unit.Consumptions; +namespace Vegasco.Server.Api.Tests.Unit.Consumptions; public class CreateConsumptionRequestValidatorTests { private readonly CreateConsumption.Validator _sut; diff --git a/tests/WebApi.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs b/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs similarity index 96% rename from tests/WebApi.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs rename to tests/Vegasco.Server.Api.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs index 14a91e8..fd34167 100644 --- a/tests/WebApi.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs @@ -1,9 +1,9 @@ using FluentAssertions; using FluentValidation.Results; using NSubstitute; -using Vegasco.WebApi.Consumptions; +using Vegasco.Server.Api.Consumptions; -namespace WebApi.Tests.Unit.Consumptions; +namespace Vegasco.Server.Api.Tests.Unit.Consumptions; public class UpdateConsumptionRequestValidatorTests { diff --git a/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj b/tests/Vegasco.Server.Api.Tests.Unit/Vegasco.Server.Api.Tests.Unit.csproj similarity index 93% rename from tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj rename to tests/Vegasco.Server.Api.Tests.Unit/Vegasco.Server.Api.Tests.Unit.csproj index 71caaf1..a2a53a7 100644 --- a/tests/WebApi.Tests.Unit/WebApi.Tests.Unit.csproj +++ b/tests/Vegasco.Server.Api.Tests.Unit/Vegasco.Server.Api.Tests.Unit.csproj @@ -26,7 +26,7 @@ - + diff --git a/vegasco-server.sln b/vegasco-server.sln deleted file mode 100644 index 2e7f0b3..0000000 --- a/vegasco-server.sln +++ /dev/null @@ -1,50 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.13.35617.110 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{089100B1-113F-4E66-888A-E83F3999EAFD}" - ProjectSection(SolutionItems) = preProject - .drone.yml = .drone.yml - Dockerfile = Dockerfile - README.md = README.md - version.json = version.json - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi", "src\WebApi\WebApi.csproj", "{1B0A04C3-E6BC-0FB7-7994-7C99BDAB1788}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.Integration", "tests\WebApi.Tests.Integration\WebApi.Tests.Integration.csproj", "{72BF8CBC-E916-1472-A1E2-8F5DCF1A95C6}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.Tests.Unit", "tests\WebApi.Tests.Unit\WebApi.Tests.Unit.csproj", "{2DD4D427-6FA5-EC56-76FC-9D71C4631E00}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1B0A04C3-E6BC-0FB7-7994-7C99BDAB1788}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B0A04C3-E6BC-0FB7-7994-7C99BDAB1788}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1B0A04C3-E6BC-0FB7-7994-7C99BDAB1788}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B0A04C3-E6BC-0FB7-7994-7C99BDAB1788}.Release|Any CPU.Build.0 = Release|Any CPU - {72BF8CBC-E916-1472-A1E2-8F5DCF1A95C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {72BF8CBC-E916-1472-A1E2-8F5DCF1A95C6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {72BF8CBC-E916-1472-A1E2-8F5DCF1A95C6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {72BF8CBC-E916-1472-A1E2-8F5DCF1A95C6}.Release|Any CPU.Build.0 = Release|Any CPU - {2DD4D427-6FA5-EC56-76FC-9D71C4631E00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2DD4D427-6FA5-EC56-76FC-9D71C4631E00}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2DD4D427-6FA5-EC56-76FC-9D71C4631E00}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2DD4D427-6FA5-EC56-76FC-9D71C4631E00}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {1B0A04C3-E6BC-0FB7-7994-7C99BDAB1788} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} - {72BF8CBC-E916-1472-A1E2-8F5DCF1A95C6} = {0AB3BF05-4346-4AA6-1389-037BE0695223} - {2DD4D427-6FA5-EC56-76FC-9D71C4631E00} = {0AB3BF05-4346-4AA6-1389-037BE0695223} - EndGlobalSection -EndGlobal diff --git a/vegasco-server.slnx b/vegasco-server.slnx index a928535..844625c 100644 --- a/vegasco-server.slnx +++ b/vegasco-server.slnx @@ -9,10 +9,10 @@ - + - - + + \ No newline at end of file -- 2.49.1 From b28bd2826be4aa10cc8dd07de265e62d1aec14d6 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 12 Jun 2025 19:12:26 +0200 Subject: [PATCH 060/150] Keep db container after apps shutdown --- src/Vegasco.Server.AppHost/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Vegasco.Server.AppHost/Program.cs b/src/Vegasco.Server.AppHost/Program.cs index 476f8c4..5d592bc 100644 --- a/src/Vegasco.Server.AppHost/Program.cs +++ b/src/Vegasco.Server.AppHost/Program.cs @@ -3,6 +3,7 @@ using Vegasco.Server.AppHost.Shared; IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder(args); IResourceBuilder postgres = builder.AddPostgres(Constants.Database.ServiceName) + .WithLifetime(ContainerLifetime.Persistent) .WithDataVolume() .AddDatabase(Constants.Database.Name); -- 2.49.1 From ada0e2f665862d2010f738f14902e0cab08200a8 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 12 Jun 2025 19:12:38 +0200 Subject: [PATCH 061/150] Use custom activity source --- .../Common/DependencyInjectionExtensions.cs | 7 +++++ .../Persistence/ApplyMigrationsService.cs | 28 +++++++++++-------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs b/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs index 041b3dd..539aef2 100644 --- a/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs +++ b/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs @@ -2,6 +2,8 @@ using FluentValidation; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Options; +using System.Diagnostics; +using System.Reflection; using Vegasco.Server.Api.Authentication; using Vegasco.Server.Api.Common; using Vegasco.Server.Api.Persistence; @@ -27,6 +29,11 @@ public static class DependencyInjectionExtensions private static IServiceCollection AddMiscellaneousServices(this IServiceCollection services) { + var assemblyName = Assembly.GetExecutingAssembly() + .GetName() + .Name ?? "Vegasco.Server.Api"; + services.AddSingleton(new ActivitySource(assemblyName)); + services.AddResponseCompression(); services.AddValidatorsFromAssemblies( diff --git a/src/Vegasco.Server.Api/Persistence/ApplyMigrationsService.cs b/src/Vegasco.Server.Api/Persistence/ApplyMigrationsService.cs index 19b9d70..3b0ffc7 100644 --- a/src/Vegasco.Server.Api/Persistence/ApplyMigrationsService.cs +++ b/src/Vegasco.Server.Api/Persistence/ApplyMigrationsService.cs @@ -1,18 +1,24 @@ using Microsoft.EntityFrameworkCore; +using System.Diagnostics; namespace Vegasco.Server.Api.Persistence; -public class ApplyMigrationsService(ILogger logger, IServiceScopeFactory scopeFactory) - : IHostedService +public class ApplyMigrationsService( + ILogger logger, + IServiceScopeFactory scopeFactory, + ActivitySource activitySource) + : IHostedService { - public async Task StartAsync(CancellationToken cancellationToken) - { - logger.LogInformation("Starting migrations"); - - using IServiceScope scope = scopeFactory.CreateScope(); - await using var dbContext = scope.ServiceProvider.GetRequiredService(); - await dbContext.Database.MigrateAsync(cancellationToken); - } + public async Task StartAsync(CancellationToken cancellationToken) + { + using var activity = activitySource.StartActivity("ApplyMigrations"); - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + logger.LogInformation("Starting migrations"); + + using IServiceScope scope = scopeFactory.CreateScope(); + await using var dbContext = scope.ServiceProvider.GetRequiredService(); + await dbContext.Database.MigrateAsync(cancellationToken); + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } \ No newline at end of file -- 2.49.1 From 9847b6e6f71b1ef99f8349081a4c5a8a19b30705 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 12 Jun 2025 19:18:17 +0200 Subject: [PATCH 062/150] Change assembly name constant spelling --- .../Common/DependencyInjectionExtensions.cs | 7 +++++-- src/Vegasco.Server.AppHost.Shared/Constants.cs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs b/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs index 539aef2..a36b59b 100644 --- a/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs +++ b/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs @@ -29,10 +29,13 @@ public static class DependencyInjectionExtensions private static IServiceCollection AddMiscellaneousServices(this IServiceCollection services) { - var assemblyName = Assembly.GetExecutingAssembly() + services.AddSingleton(sp => + { + var assemblyName = Assembly.GetExecutingAssembly() .GetName() .Name ?? "Vegasco.Server.Api"; - services.AddSingleton(new ActivitySource(assemblyName)); + return new ActivitySource(assemblyName); + }); services.AddResponseCompression(); diff --git a/src/Vegasco.Server.AppHost.Shared/Constants.cs b/src/Vegasco.Server.AppHost.Shared/Constants.cs index 9c1dc4c..74e2769 100644 --- a/src/Vegasco.Server.AppHost.Shared/Constants.cs +++ b/src/Vegasco.Server.AppHost.Shared/Constants.cs @@ -4,7 +4,7 @@ public static class Constants { public static class Projects { - public const string Api = "Vegasco_Server_Api"; + public const string Api = "Vegasco.Server.Api"; } public static class Database -- 2.49.1 From 16bc2507898dde014c29df781f3c0170727d213e Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 12 Jun 2025 19:20:22 +0200 Subject: [PATCH 063/150] Fix project name spelling --- src/Vegasco.Server.AppHost.Shared/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Vegasco.Server.AppHost.Shared/Constants.cs b/src/Vegasco.Server.AppHost.Shared/Constants.cs index 74e2769..b220976 100644 --- a/src/Vegasco.Server.AppHost.Shared/Constants.cs +++ b/src/Vegasco.Server.AppHost.Shared/Constants.cs @@ -4,7 +4,7 @@ public static class Constants { public static class Projects { - public const string Api = "Vegasco.Server.Api"; + public const string Api = "Vegasco-Server-Api"; } public static class Database -- 2.49.1 From 7aa859953593207001cbfe32876cfb5519eb4bc1 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 13 Jun 2025 19:39:37 +0200 Subject: [PATCH 064/150] Update request localization --- .../Common/DependencyInjectionExtensions.cs | 25 ++++++++++++++++--- .../Common/StartupExtensions.cs | 10 +------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs b/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs index a36b59b..7cccca9 100644 --- a/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs +++ b/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs @@ -3,7 +3,9 @@ using FluentValidation; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Options; using System.Diagnostics; +using System.Globalization; using System.Reflection; +using Microsoft.AspNetCore.Localization; using Vegasco.Server.Api.Authentication; using Vegasco.Server.Api.Common; using Vegasco.Server.Api.Persistence; @@ -29,11 +31,11 @@ public static class DependencyInjectionExtensions private static IServiceCollection AddMiscellaneousServices(this IServiceCollection services) { - services.AddSingleton(sp => + services.AddSingleton(() => { - var assemblyName = Assembly.GetExecutingAssembly() - .GetName() - .Name ?? "Vegasco.Server.Api"; + string assemblyName = Assembly.GetExecutingAssembly() + .GetName() + .Name ?? "Vegasco.Server.Api"; return new ActivitySource(assemblyName); }); @@ -49,6 +51,21 @@ public static class DependencyInjectionExtensions services.AddHttpContextAccessor(); services.AddHostedService(); + + services.AddRequestLocalization(o => + { + string[] cultures = + [ + "en-US", + "en", + "de-DE", + "de" + ]; + + o.SetDefaultCulture(cultures[0]) + .AddSupportedCultures(cultures) + .AddSupportedUICultures(cultures); + }); return services; } diff --git a/src/Vegasco.Server.Api/Common/StartupExtensions.cs b/src/Vegasco.Server.Api/Common/StartupExtensions.cs index be0f48e..4c1d06d 100644 --- a/src/Vegasco.Server.Api/Common/StartupExtensions.cs +++ b/src/Vegasco.Server.Api/Common/StartupExtensions.cs @@ -23,15 +23,7 @@ internal static class StartupExtensions { app.UseRequestLocalization(o => { - o.SupportedCultures = - [ - new CultureInfo("en") - ]; - - o.SupportedUICultures = o.SupportedCultures; - - CultureInfo defaultCulture = o.SupportedCultures[0]; - o.DefaultRequestCulture = new RequestCulture(defaultCulture); + o.ApplyCurrentCultureToResponseHeaders = true; }); app.UseHttpsRedirection(); -- 2.49.1 From e29c5b2458fe667d99e5c2a417870f7c3cfc7d5c Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 13 Jun 2025 19:39:46 +0200 Subject: [PATCH 065/150] Return DateTimeOffset --- src/Vegasco.Server.Api/Info/GetServerInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Vegasco.Server.Api/Info/GetServerInfo.cs b/src/Vegasco.Server.Api/Info/GetServerInfo.cs index 730b83b..2e82096 100644 --- a/src/Vegasco.Server.Api/Info/GetServerInfo.cs +++ b/src/Vegasco.Server.Api/Info/GetServerInfo.cs @@ -7,7 +7,7 @@ public class GetServerInfo public record Response( string FullVersion, string CommitId, - DateTime CommitDate, + DateTimeOffset CommitDate, string Environment); public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) @@ -23,7 +23,7 @@ public class GetServerInfo return TypedResults.Ok(new Response( ThisAssembly.AssemblyInformationalVersion, ThisAssembly.GitCommitId, - ThisAssembly.GitCommitDate, + new DateTimeOffset(ThisAssembly.GitCommitDate, TimeSpan.Zero), environment.EnvironmentName)); } } \ No newline at end of file -- 2.49.1 From d80a53761db85f0fd2c626b9941d009aa8d0f821 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 13 Jun 2025 19:48:07 +0200 Subject: [PATCH 066/150] Generate Angular app --- src/Vegasco-Web/.editorconfig | 17 + src/Vegasco-Web/.gitignore | 42 + src/Vegasco-Web/.vscode/extensions.json | 4 + src/Vegasco-Web/.vscode/launch.json | 20 + src/Vegasco-Web/.vscode/tasks.json | 42 + src/Vegasco-Web/README.md | 59 + src/Vegasco-Web/angular.json | 98 + src/Vegasco-Web/package.json | 36 + src/Vegasco-Web/pnpm-lock.yaml | 5108 +++++++++++++++++++++++ src/Vegasco-Web/public/favicon.ico | Bin 0 -> 15086 bytes src/Vegasco-Web/src/app/app.config.ts | 12 + src/Vegasco-Web/src/app/app.html | 336 ++ src/Vegasco-Web/src/app/app.routes.ts | 3 + src/Vegasco-Web/src/app/app.scss | 0 src/Vegasco-Web/src/app/app.spec.ts | 23 + src/Vegasco-Web/src/app/app.ts | 12 + src/Vegasco-Web/src/index.html | 13 + src/Vegasco-Web/src/main.ts | 6 + src/Vegasco-Web/src/styles.scss | 1 + src/Vegasco-Web/tsconfig.app.json | 15 + src/Vegasco-Web/tsconfig.json | 34 + src/Vegasco-Web/tsconfig.spec.json | 14 + 22 files changed, 5895 insertions(+) create mode 100644 src/Vegasco-Web/.editorconfig create mode 100644 src/Vegasco-Web/.gitignore create mode 100644 src/Vegasco-Web/.vscode/extensions.json create mode 100644 src/Vegasco-Web/.vscode/launch.json create mode 100644 src/Vegasco-Web/.vscode/tasks.json create mode 100644 src/Vegasco-Web/README.md create mode 100644 src/Vegasco-Web/angular.json create mode 100644 src/Vegasco-Web/package.json create mode 100644 src/Vegasco-Web/pnpm-lock.yaml create mode 100644 src/Vegasco-Web/public/favicon.ico create mode 100644 src/Vegasco-Web/src/app/app.config.ts create mode 100644 src/Vegasco-Web/src/app/app.html create mode 100644 src/Vegasco-Web/src/app/app.routes.ts create mode 100644 src/Vegasco-Web/src/app/app.scss create mode 100644 src/Vegasco-Web/src/app/app.spec.ts create mode 100644 src/Vegasco-Web/src/app/app.ts create mode 100644 src/Vegasco-Web/src/index.html create mode 100644 src/Vegasco-Web/src/main.ts create mode 100644 src/Vegasco-Web/src/styles.scss create mode 100644 src/Vegasco-Web/tsconfig.app.json create mode 100644 src/Vegasco-Web/tsconfig.json create mode 100644 src/Vegasco-Web/tsconfig.spec.json diff --git a/src/Vegasco-Web/.editorconfig b/src/Vegasco-Web/.editorconfig new file mode 100644 index 0000000..f166060 --- /dev/null +++ b/src/Vegasco-Web/.editorconfig @@ -0,0 +1,17 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single +ij_typescript_use_double_quotes = false + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/src/Vegasco-Web/.gitignore b/src/Vegasco-Web/.gitignore new file mode 100644 index 0000000..cc7b141 --- /dev/null +++ b/src/Vegasco-Web/.gitignore @@ -0,0 +1,42 @@ +# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. + +# Compiled output +/dist +/tmp +/out-tsc +/bazel-out + +# Node +/node_modules +npm-debug.log +yarn-error.log + +# IDEs and editors +.idea/ +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# Visual Studio Code +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# Miscellaneous +/.angular/cache +.sass-cache/ +/connect.lock +/coverage +/libpeerconnection.log +testem.log +/typings + +# System files +.DS_Store +Thumbs.db diff --git a/src/Vegasco-Web/.vscode/extensions.json b/src/Vegasco-Web/.vscode/extensions.json new file mode 100644 index 0000000..77b3745 --- /dev/null +++ b/src/Vegasco-Web/.vscode/extensions.json @@ -0,0 +1,4 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 + "recommendations": ["angular.ng-template"] +} diff --git a/src/Vegasco-Web/.vscode/launch.json b/src/Vegasco-Web/.vscode/launch.json new file mode 100644 index 0000000..925af83 --- /dev/null +++ b/src/Vegasco-Web/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "ng serve", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: start", + "url": "http://localhost:4200/" + }, + { + "name": "ng test", + "type": "chrome", + "request": "launch", + "preLaunchTask": "npm: test", + "url": "http://localhost:9876/debug.html" + } + ] +} diff --git a/src/Vegasco-Web/.vscode/tasks.json b/src/Vegasco-Web/.vscode/tasks.json new file mode 100644 index 0000000..a298b5b --- /dev/null +++ b/src/Vegasco-Web/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "start", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + }, + { + "type": "npm", + "script": "test", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "pattern": "$tsc", + "background": { + "activeOnStart": true, + "beginsPattern": { + "regexp": "(.*?)" + }, + "endsPattern": { + "regexp": "bundle generation complete" + } + } + } + } + ] +} diff --git a/src/Vegasco-Web/README.md b/src/Vegasco-Web/README.md new file mode 100644 index 0000000..f4a28cc --- /dev/null +++ b/src/Vegasco-Web/README.md @@ -0,0 +1,59 @@ +# VegascoWeb + +This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.0.1. + +## Development server + +To start a local development server, run: + +```bash +ng serve +``` + +Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. + +## Code scaffolding + +Angular CLI includes powerful code scaffolding tools. To generate a new component, run: + +```bash +ng generate component component-name +``` + +For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: + +```bash +ng generate --help +``` + +## Building + +To build the project run: + +```bash +ng build +``` + +This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. + +## Running unit tests + +To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command: + +```bash +ng test +``` + +## Running end-to-end tests + +For end-to-end (e2e) testing, run: + +```bash +ng e2e +``` + +Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. + +## Additional Resources + +For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. diff --git a/src/Vegasco-Web/angular.json b/src/Vegasco-Web/angular.json new file mode 100644 index 0000000..69eea0e --- /dev/null +++ b/src/Vegasco-Web/angular.json @@ -0,0 +1,98 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "Vegasco-Web": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular/build:application", + "options": { + "browser": "src/main.ts", + "polyfills": [ + "zone.js" + ], + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": [ + "src/styles.scss" + ] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kB", + "maximumError": "1MB" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "4kB", + "maximumError": "8kB" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular/build:dev-server", + "configurations": { + "production": { + "buildTarget": "Vegasco-Web:build:production" + }, + "development": { + "buildTarget": "Vegasco-Web:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular/build:extract-i18n" + }, + "test": { + "builder": "@angular/build:karma", + "options": { + "polyfills": [ + "zone.js", + "zone.js/testing" + ], + "tsConfig": "tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": [ + "src/styles.scss" + ] + } + } + } + } + } +} diff --git a/src/Vegasco-Web/package.json b/src/Vegasco-Web/package.json new file mode 100644 index 0000000..a2bfe60 --- /dev/null +++ b/src/Vegasco-Web/package.json @@ -0,0 +1,36 @@ +{ + "name": "vegasco-web", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/common": "^20.0.3", + "@angular/compiler": "^20.0.3", + "@angular/core": "^20.0.3", + "@angular/forms": "^20.0.3", + "@angular/platform-browser": "^20.0.3", + "@angular/router": "^20.0.3", + "rxjs": "~7.8.2", + "tslib": "^2.8.1", + "zone.js": "~0.15.1" + }, + "devDependencies": { + "@angular/build": "^20.0.2", + "@angular/cli": "^20.0.2", + "@angular/compiler-cli": "^20.0.3", + "@types/jasmine": "~5.1.8", + "jasmine-core": "~5.7.1", + "karma": "~6.4.4", + "karma-chrome-launcher": "~3.2.0", + "karma-coverage": "~2.2.1", + "karma-jasmine": "~5.1.0", + "karma-jasmine-html-reporter": "~2.1.0", + "typescript": "~5.8.3" + } +} diff --git a/src/Vegasco-Web/pnpm-lock.yaml b/src/Vegasco-Web/pnpm-lock.yaml new file mode 100644 index 0000000..dedd530 --- /dev/null +++ b/src/Vegasco-Web/pnpm-lock.yaml @@ -0,0 +1,5108 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@angular/common': + specifier: ^20.0.3 + version: 20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/compiler': + specifier: ^20.0.3 + version: 20.0.3 + '@angular/core': + specifier: ^20.0.3 + version: 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/forms': + specifier: ^20.0.3 + version: 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/platform-browser': + specifier: ^20.0.3 + version: 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)) + '@angular/router': + specifier: ^20.0.3 + version: 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + rxjs: + specifier: ~7.8.2 + version: 7.8.2 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + zone.js: + specifier: ~0.15.1 + version: 0.15.1 + devDependencies: + '@angular/build': + specifier: ^20.0.2 + version: 20.0.2(@angular/compiler-cli@20.0.3(@angular/compiler@20.0.3)(typescript@5.8.3))(@angular/compiler@20.0.3)(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.0.1)(chokidar@4.0.3)(karma@6.4.4)(postcss@8.5.5)(tslib@2.8.1)(typescript@5.8.3) + '@angular/cli': + specifier: ^20.0.2 + version: 20.0.2(@types/node@24.0.1)(chokidar@4.0.3) + '@angular/compiler-cli': + specifier: ^20.0.3 + version: 20.0.3(@angular/compiler@20.0.3)(typescript@5.8.3) + '@types/jasmine': + specifier: ~5.1.8 + version: 5.1.8 + jasmine-core: + specifier: ~5.7.1 + version: 5.7.1 + karma: + specifier: ~6.4.4 + version: 6.4.4 + karma-chrome-launcher: + specifier: ~3.2.0 + version: 3.2.0 + karma-coverage: + specifier: ~2.2.1 + version: 2.2.1 + karma-jasmine: + specifier: ~5.1.0 + version: 5.1.0(karma@6.4.4) + karma-jasmine-html-reporter: + specifier: ~2.1.0 + version: 2.1.0(jasmine-core@5.7.1)(karma-jasmine@5.1.0(karma@6.4.4))(karma@6.4.4) + typescript: + specifier: ~5.8.3 + version: 5.8.3 + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@angular-devkit/architect@0.2000.2': + resolution: {integrity: sha512-adJYWJWuyXFtCOg2lZTV/7CImf4ifxd6c//VXuq5kx7AiSGTIH5Nf2xTQe8ZAZqytUmDgnoNMDhGRQ9b3C5TnA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@angular-devkit/core@20.0.2': + resolution: {integrity: sha512-qqTSpcIw+TqJ6u/tkQzqgpwVelHsHr8Jhws1Vlx6E0L6E+cRILBK48i9ttE+oYkQlcopQ3VZAmzcZodXJ1SQ9Q==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^4.0.0 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/schematics@20.0.2': + resolution: {integrity: sha512-r1aSZhcadLtUMhzUUfy+pkJdZW93z8WQtpGR24y88yFpPgDL5kY85VSlOzjGgo1vEs8Dd7ADcOcsVsUW8MxQ3A==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@angular/build@20.0.2': + resolution: {integrity: sha512-nxha/dncAwEbY0nkgDWeiWSi+MSCJBuQbFf5bjTZ+pu0fS+5SOQllZKzZE9H+dms/JsLHm2YmPiScIYVvUenDw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + '@angular/compiler': ^20.0.0 + '@angular/compiler-cli': ^20.0.0 + '@angular/core': ^20.0.0 + '@angular/localize': ^20.0.0 + '@angular/platform-browser': ^20.0.0 + '@angular/platform-server': ^20.0.0 + '@angular/service-worker': ^20.0.0 + '@angular/ssr': ^20.0.2 + karma: ^6.4.0 + less: ^4.2.0 + ng-packagr: ^20.0.0 + postcss: ^8.4.0 + tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 + tslib: ^2.3.0 + typescript: '>=5.8 <5.9' + vitest: ^3.1.1 + peerDependenciesMeta: + '@angular/core': + optional: true + '@angular/localize': + optional: true + '@angular/platform-browser': + optional: true + '@angular/platform-server': + optional: true + '@angular/service-worker': + optional: true + '@angular/ssr': + optional: true + karma: + optional: true + less: + optional: true + ng-packagr: + optional: true + postcss: + optional: true + tailwindcss: + optional: true + vitest: + optional: true + + '@angular/cli@20.0.2': + resolution: {integrity: sha512-LzBONPETA1uCZuylgZRYe+vImf8i+rRrwAgOBHWbW2wxut9ZQ8ZFwQgNkjvDhE7DLmsFV+GskfAs5+Td/5LZwA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + + '@angular/common@20.0.3': + resolution: {integrity: sha512-HqqVqaj+xzByWJOIrONVRkpvM6mRuGmC+m9wKixhc9f+xXsymVTBR6xg+G/RwyYP2NuC5chxIZbaJTz2Hj+6+g==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/core': 20.0.3 + rxjs: ^6.5.3 || ^7.4.0 + + '@angular/compiler-cli@20.0.3': + resolution: {integrity: sha512-u+fYnx1sRrwL0fd8kaAD2LqJjfe/Zj7zyOv0A3Ue7r8jzdNsPU8MWr/QyBaWlqSpPEpR+kD3xmDvRT9ra9RTBA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@angular/compiler': 20.0.3 + typescript: '>=5.8 <5.9' + peerDependenciesMeta: + typescript: + optional: true + + '@angular/compiler@20.0.3': + resolution: {integrity: sha512-CShPNvqqV5Cleyho8CKtcFlt7l2thHPUdXZPtKHH3Zf43KojvJbJksZLBz6ZbyoQdgxNMYSfbh4h0UbSGtPOzQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@angular/core@20.0.3': + resolution: {integrity: sha512-kB6w1bQgClfmkTbWJeD3vSLqX0e3uSaJD6KJ7XXT1IEaqUs4J+mKRKHQyxpJlpdUb7R+jDaHSM/vrVF15/L2rA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/compiler': 20.0.3 + rxjs: ^6.5.3 || ^7.4.0 + zone.js: ~0.15.0 + peerDependenciesMeta: + '@angular/compiler': + optional: true + zone.js: + optional: true + + '@angular/forms@20.0.3': + resolution: {integrity: sha512-tb4M+c+/JnmPmtTb3+Si/DWGttnCEW5rvi4u55q+EFxYGQO0GeHa53yQTl1e2ngQLT9/kgmDAsJ2mt1Ql9N6xg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/common': 20.0.3 + '@angular/core': 20.0.3 + '@angular/platform-browser': 20.0.3 + rxjs: ^6.5.3 || ^7.4.0 + + '@angular/platform-browser@20.0.3': + resolution: {integrity: sha512-cba0bibw9dJ8b+a2a8mwkiq5/HPiakY9P5OiJEVefN+2V/K9CND/pW+KIbW0/P6KhSSDQ29xgcGRseVtkjYLmg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/animations': 20.0.3 + '@angular/common': 20.0.3 + '@angular/core': 20.0.3 + peerDependenciesMeta: + '@angular/animations': + optional: true + + '@angular/router@20.0.3': + resolution: {integrity: sha512-FY2kMZjLh7NUKjSaZ1K26azl67T4aVnOD8PE/w1Ih3eQmSIlHniNP1NmCGMUy6t1O/ZV6sCSKkA5AZFv18wzIQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@angular/common': 20.0.3 + '@angular/core': 20.0.3 + '@angular/platform-browser': 20.0.3 + rxjs: ^6.5.3 || ^7.4.0 + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.27.5': + resolution: {integrity: sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.27.1': + resolution: {integrity: sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.27.4': + resolution: {integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.27.5': + resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.1': + resolution: {integrity: sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.27.3': + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-split-export-declaration@7.24.7': + resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.27.6': + resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.5': + resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.27.4': + resolution: {integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.27.6': + resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} + engines: {node: '>=6.9.0'} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@esbuild/aix-ppc64@0.25.5': + resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.5': + resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.5': + resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.5': + resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.5': + resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.5': + resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.5': + resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.5': + resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.5': + resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.5': + resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.5': + resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.5': + resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.5': + resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.5': + resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.5': + resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.5': + resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.5': + resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.5': + resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.5': + resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.5': + resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.5': + resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.5': + resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.5': + resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.5': + resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.5': + resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@inquirer/checkbox@4.1.8': + resolution: {integrity: sha512-d/QAsnwuHX2OPolxvYcgSj7A9DO9H6gVOy2DvBTx+P2LH2iRTo/RSGV3iwCzW024nP9hw98KIuDmdyhZQj1UQg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.10': + resolution: {integrity: sha512-FxbQ9giWxUWKUk2O5XZ6PduVnH2CZ/fmMKMBkH71MHJvWr7WL5AHKevhzF1L5uYWB2P548o1RzVxrNd3dpmk6g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.12': + resolution: {integrity: sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.1.13': + resolution: {integrity: sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@4.2.13': + resolution: {integrity: sha512-WbicD9SUQt/K8O5Vyk9iC2ojq5RHoCLK6itpp2fHsWe44VxxcA9z3GTWlvjSTGmMQpZr+lbVmrxdHcumJoLbMA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@4.0.15': + resolution: {integrity: sha512-4Y+pbr/U9Qcvf+N/goHzPEXiHH8680lM3Dr3Y9h9FFw4gHS+zVpbj8LfbKWIb/jayIB4aSO4pWiBTrBYWkvi5A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.12': + resolution: {integrity: sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==} + engines: {node: '>=18'} + + '@inquirer/input@4.1.12': + resolution: {integrity: sha512-xJ6PFZpDjC+tC1P8ImGprgcsrzQRsUh9aH3IZixm1lAZFK49UGHxM3ltFfuInN2kPYNfyoPRh+tU4ftsjPLKqQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@3.0.15': + resolution: {integrity: sha512-xWg+iYfqdhRiM55MvqiTCleHzszpoigUpN5+t1OMcRkJrUrw7va3AzXaxvS+Ak7Gny0j2mFSTv2JJj8sMtbV2g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@4.0.15': + resolution: {integrity: sha512-75CT2p43DGEnfGTaqFpbDC2p2EEMrq0S+IRrf9iJvYreMy5mAWj087+mdKyLHapUEPLjN10mNvABpGbk8Wdraw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.5.1': + resolution: {integrity: sha512-5AOrZPf2/GxZ+SDRZ5WFplCA2TAQgK3OYrXCYmJL5NaTu4ECcoWFlfUZuw7Es++6Njv7iu/8vpYJhuzxUH76Vg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.1.3': + resolution: {integrity: sha512-7XrV//6kwYumNDSsvJIPeAqa8+p7GJh7H5kRuxirct2cgOcSWwwNGoXDRgpNFbY/MG2vQ4ccIWCi8+IXXyFMZA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.0.15': + resolution: {integrity: sha512-YBMwPxYBrADqyvP4nNItpwkBnGGglAvCLVW8u4pRmmvOsHUtCAUIMbUrLX5B3tFL1/WsLGdQ2HNzkqswMs5Uaw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.2.3': + resolution: {integrity: sha512-OAGhXU0Cvh0PhLz9xTF/kx6g6x+sP+PcyTiLvCrewI99P3BBeexD+VbuwkNDvqGkk3y2h5ZiWLeRP7BFlhkUDg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@1.5.5': + resolution: {integrity: sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.7': + resolution: {integrity: sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@listr2/prompt-adapter-inquirer@2.0.22': + resolution: {integrity: sha512-hV36ZoY+xKL6pYOt1nPNnkciFkn89KZwqLhAFzJvYysAvL5uBQdiADZx/8bIDXIukzzwG0QlPYolgMzQUtKgpQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@inquirer/prompts': '>= 3 < 8' + + '@lmdb/lmdb-darwin-arm64@3.3.0': + resolution: {integrity: sha512-LipbQobyEfQtu8WixasaFUZZ+JCGlho4OWwWIQ5ol0rB1RKkcZvypu7sS1CBvofBGVAa3vbOh8IOGQMrbmL5dg==} + cpu: [arm64] + os: [darwin] + + '@lmdb/lmdb-darwin-x64@3.3.0': + resolution: {integrity: sha512-yA+9P+ZeA3vg76BLXWeUomIAjxfmSmR2eg8fueHXDg5Xe1Xmkl9JCKuHXUhtJ+mMVcH12d5k4kJBLbyXTadfGQ==} + cpu: [x64] + os: [darwin] + + '@lmdb/lmdb-linux-arm64@3.3.0': + resolution: {integrity: sha512-OeWvSgjXXZ/zmtLqqL78I3910F6UYpUubmsUU+iBHo6nTtjkpXms95rJtGrjkWQqwswKBD7xSMplbYC4LEsiPA==} + cpu: [arm64] + os: [linux] + + '@lmdb/lmdb-linux-arm@3.3.0': + resolution: {integrity: sha512-EDYrW9kle+8wI19JCj/PhRnGoCN9bked5cdOPdo1wdgH/HzjgoLPFTn9DHlZccgTEVhp3O+bpWXdN/rWySVvjw==} + cpu: [arm] + os: [linux] + + '@lmdb/lmdb-linux-x64@3.3.0': + resolution: {integrity: sha512-wDd02mt5ScX4+xd6g78zKBr6ojpgCJCTrllCAabjgap5FzuETqOqaQfKhO+tJuGWv/J5q+GIds6uY7rNFueOxg==} + cpu: [x64] + os: [linux] + + '@lmdb/lmdb-win32-arm64@3.3.0': + resolution: {integrity: sha512-COotWhHJgzXULLiEjOgWQwqig6PoA+6ji6W+sDl6M1HhMXWIymEVHGs0edsVSNtsNSCAWMxJgR3asv6FNX/2EA==} + cpu: [arm64] + os: [win32] + + '@lmdb/lmdb-win32-x64@3.3.0': + resolution: {integrity: sha512-kqUgQH+l8HDbkAapx+aoko7Ez4X4DqkIraOqY/k0QY5EN/iialVlFpBUXh4wFXzirdmEVjbIUMrceUh0Kh8LeA==} + cpu: [x64] + os: [win32] + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + cpu: [arm64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + cpu: [x64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + cpu: [arm64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + cpu: [arm] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + cpu: [x64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + cpu: [x64] + os: [win32] + + '@napi-rs/nice-android-arm-eabi@1.0.1': + resolution: {integrity: sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/nice-android-arm64@1.0.1': + resolution: {integrity: sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/nice-darwin-arm64@1.0.1': + resolution: {integrity: sha512-91k3HEqUl2fsrz/sKkuEkscj6EAj3/eZNCLqzD2AA0TtVbkQi8nqxZCZDMkfklULmxLkMxuUdKe7RvG/T6s2AA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/nice-darwin-x64@1.0.1': + resolution: {integrity: sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/nice-freebsd-x64@1.0.1': + resolution: {integrity: sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/nice-linux-arm-gnueabihf@1.0.1': + resolution: {integrity: sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/nice-linux-arm64-gnu@1.0.1': + resolution: {integrity: sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/nice-linux-arm64-musl@1.0.1': + resolution: {integrity: sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/nice-linux-ppc64-gnu@1.0.1': + resolution: {integrity: sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==} + engines: {node: '>= 10'} + cpu: [ppc64] + os: [linux] + + '@napi-rs/nice-linux-riscv64-gnu@1.0.1': + resolution: {integrity: sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + + '@napi-rs/nice-linux-s390x-gnu@1.0.1': + resolution: {integrity: sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==} + engines: {node: '>= 10'} + cpu: [s390x] + os: [linux] + + '@napi-rs/nice-linux-x64-gnu@1.0.1': + resolution: {integrity: sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/nice-linux-x64-musl@1.0.1': + resolution: {integrity: sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/nice-win32-arm64-msvc@1.0.1': + resolution: {integrity: sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/nice-win32-ia32-msvc@1.0.1': + resolution: {integrity: sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/nice-win32-x64-msvc@1.0.1': + resolution: {integrity: sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/nice@1.0.1': + resolution: {integrity: sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==} + engines: {node: '>= 10'} + + '@npmcli/agent@3.0.0': + resolution: {integrity: sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@npmcli/fs@4.0.0': + resolution: {integrity: sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@npmcli/git@6.0.3': + resolution: {integrity: sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@npmcli/installed-package-contents@3.0.0': + resolution: {integrity: sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + '@npmcli/node-gyp@4.0.0': + resolution: {integrity: sha512-+t5DZ6mO/QFh78PByMq1fGSAub/agLJZDRfJRMeOSNCt8s9YVlTjmGpIPwPhvXTGUIJk+WszlT0rQa1W33yzNA==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@npmcli/package-json@6.2.0': + resolution: {integrity: sha512-rCNLSB/JzNvot0SEyXqWZ7tX2B5dD2a1br2Dp0vSYVo5jh8Z0EZ7lS9TsZ1UtziddB1UfNUaMCc538/HztnJGA==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@npmcli/promise-spawn@8.0.2': + resolution: {integrity: sha512-/bNJhjc+o6qL+Dwz/bqfTQClkEO5nTQ1ZEcdCkAQjhkZMHIh22LPG7fNh1enJP1NKWDqYiiABnjFCY7E0zHYtQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@npmcli/redact@3.2.2': + resolution: {integrity: sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@npmcli/run-script@9.1.0': + resolution: {integrity: sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@rollup/rollup-android-arm-eabi@4.40.2': + resolution: {integrity: sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.40.2': + resolution: {integrity: sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.40.2': + resolution: {integrity: sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.40.2': + resolution: {integrity: sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.40.2': + resolution: {integrity: sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.40.2': + resolution: {integrity: sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.40.2': + resolution: {integrity: sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.40.2': + resolution: {integrity: sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.40.2': + resolution: {integrity: sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.40.2': + resolution: {integrity: sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.40.2': + resolution: {integrity: sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': + resolution: {integrity: sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.40.2': + resolution: {integrity: sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.40.2': + resolution: {integrity: sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.40.2': + resolution: {integrity: sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.40.2': + resolution: {integrity: sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.40.2': + resolution: {integrity: sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.40.2': + resolution: {integrity: sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.40.2': + resolution: {integrity: sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.40.2': + resolution: {integrity: sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==} + cpu: [x64] + os: [win32] + + '@schematics/angular@20.0.2': + resolution: {integrity: sha512-TyF+/hV+8flAa/Vu8xOQF241Syg9rdbZD1dARdm6edbLo8nwxmHdRsIulRektb7oD5CpTnxpvrcNJjX77nhv6A==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@sigstore/bundle@3.1.0': + resolution: {integrity: sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@sigstore/core@2.0.0': + resolution: {integrity: sha512-nYxaSb/MtlSI+JWcwTHQxyNmWeWrUXJJ/G4liLrGG7+tS4vAz6LF3xRXqLH6wPIVUoZQel2Fs4ddLx4NCpiIYg==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@sigstore/protobuf-specs@0.4.3': + resolution: {integrity: sha512-fk2zjD9117RL9BjqEwF7fwv7Q/P9yGsMV4MUJZ/DocaQJ6+3pKr+syBq1owU5Q5qGw5CUbXzm+4yJ2JVRDQeSA==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@sigstore/sign@3.1.0': + resolution: {integrity: sha512-knzjmaOHOov1Ur7N/z4B1oPqZ0QX5geUfhrVaqVlu+hl0EAoL4o+l0MSULINcD5GCWe3Z0+YJO8ues6vFlW0Yw==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@sigstore/tuf@3.1.1': + resolution: {integrity: sha512-eFFvlcBIoGwVkkwmTi/vEQFSva3xs5Ot3WmBcjgjVdiaoelBLQaQ/ZBfhlG0MnG0cmTYScPpk7eDdGDWUcFUmg==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@sigstore/verify@2.1.1': + resolution: {integrity: sha512-hVJD77oT67aowHxwT4+M6PGOp+E2LtLdTK3+FC0lBO9T7sYwItDMXZ7Z07IDCvR1M717a4axbIWckrW67KMP/w==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + + '@tufjs/canonical-json@2.0.0': + resolution: {integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==} + engines: {node: ^16.14.0 || >=18.0.0} + + '@tufjs/models@3.0.1': + resolution: {integrity: sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==} + engines: {node: ^18.17.0 || >=20.5.0} + + '@types/cors@2.8.19': + resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/jasmine@5.1.8': + resolution: {integrity: sha512-u7/CnvRdh6AaaIzYjCgUuVbREFgulhX05Qtf6ZtW+aOcjCKKVvKgpkPYJBFTZSHtFBYimzU4zP0V2vrEsq9Wcg==} + + '@types/node@24.0.1': + resolution: {integrity: sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==} + + '@vitejs/plugin-basic-ssl@2.0.0': + resolution: {integrity: sha512-gc9Tjg8bUxBVSTzeWT3Njc0Cl3PakHFKdNfABnZWiUgbxqmHDEn7uECv3fHVylxoYgNzAcmU7ZrILz+BwSo3sA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + peerDependencies: + vite: ^6.0.0 + + '@yarnpkg/lockfile@1.1.0': + resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} + + abbrev@3.0.1: + resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} + engines: {node: ^18.17.0 || >=20.5.0} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-escapes@7.0.0: + resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} + engines: {node: '>=18'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64id@2.0.0: + resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} + engines: {node: ^4.5.0 || >= 5.9} + + beasties@0.3.4: + resolution: {integrity: sha512-NmzN1zN1cvGccXFyZ73335+ASXwBlVWcUPssiUDIlFdfyatHPRRufjCd5w8oPaQPvVnf9ELklaCGb1gi9FBwIw==} + engines: {node: '>=14.0.0'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.25.0: + resolution: {integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cacache@19.0.1: + resolution: {integrity: sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + caniuse-lite@1.0.30001723: + resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==} + + chalk@5.4.1: + resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + + css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + + custom-event@1.0.1: + resolution: {integrity: sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==} + + date-format@4.0.14: + resolution: {integrity: sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==} + engines: {node: '>=4.0'} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + di@0.0.1: + resolution: {integrity: sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==} + + dom-serialize@2.2.1: + resolution: {integrity: sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.167: + resolution: {integrity: sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==} + + emoji-regex@10.4.0: + resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + + engine.io@6.6.4: + resolution: {integrity: sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==} + engines: {node: '>=10.2.0'} + + ent@2.2.2: + resolution: {integrity: sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==} + engines: {node: '>= 0.4'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + esbuild@0.25.5: + resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + exponential-backoff@3.1.2: + resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.0.6: + resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs-minipass@3.0.3: + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.3.0: + resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hosted-git-info@8.1.0: + resolution: {integrity: sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==} + engines: {node: ^18.17.0 || >=20.5.0} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + htmlparser2@10.0.0: + resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore-walk@7.0.0: + resolution: {integrity: sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + immutable@5.1.3: + resolution: {integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@5.0.0: + resolution: {integrity: sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==} + engines: {node: ^18.17.0 || >=20.5.0} + + ip-address@9.0.5: + resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} + engines: {node: '>= 12'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-fullwidth-code-point@5.0.0: + resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} + engines: {node: '>=18'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + isbinaryfile@4.0.10: + resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} + engines: {node: '>= 8.0.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jasmine-core@4.6.1: + resolution: {integrity: sha512-VYz/BjjmC3klLJlLwA4Kw8ytk0zDSmbbDLNs794VnWmkcCB7I9aAL/D48VNQtmITyPvea2C3jdUMfc3kAoy0PQ==} + + jasmine-core@5.7.1: + resolution: {integrity: sha512-QnurrtpKsPoixxG2R3d1xP0St/2kcX5oTZyDyQJMY+Vzi/HUlu1kGm+2V8Tz+9lV991leB1l0xcsyz40s9xOOw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsbn@1.1.0: + resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@4.0.0: + resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==} + engines: {node: ^18.17.0 || >=20.5.0} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonparse@1.3.1: + resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} + engines: {'0': node >= 0.2.0} + + karma-chrome-launcher@3.2.0: + resolution: {integrity: sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==} + + karma-coverage@2.2.1: + resolution: {integrity: sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==} + engines: {node: '>=10.0.0'} + + karma-jasmine-html-reporter@2.1.0: + resolution: {integrity: sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==} + peerDependencies: + jasmine-core: ^4.0.0 || ^5.0.0 + karma: ^6.0.0 + karma-jasmine: ^5.0.0 + + karma-jasmine@5.1.0: + resolution: {integrity: sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==} + engines: {node: '>=12'} + peerDependencies: + karma: ^6.0.0 + + karma@6.4.4: + resolution: {integrity: sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==} + engines: {node: '>= 10'} + hasBin: true + + listr2@8.3.3: + resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} + engines: {node: '>=18.0.0'} + + lmdb@3.3.0: + resolution: {integrity: sha512-MgJocUI6QEiSXQBFWLeyo1R7eQj8Rke5dlPxX0KFwli8/bsCxpM/KbXO5y0qmV/5llQ3wpneDWcTYxa+4vn8iQ==} + hasBin: true + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@6.0.0: + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} + engines: {node: '>=18'} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + log4js@6.9.1: + resolution: {integrity: sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==} + engines: {node: '>=8.0'} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-fetch-happen@14.0.3: + resolution: {integrity: sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass-collect@2.0.1: + resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass-fetch@4.0.1: + resolution: {integrity: sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} + hasBin: true + + msgpackr@1.11.4: + resolution: {integrity: sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==} + + mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + + node-gyp@11.2.0: + resolution: {integrity: sha512-T0S1zqskVUSxcsSTkAsLc7xCycrRYmtDHadDinzocrThjyQCn5kMlEBSj6H4qDbgsIOSLmmlRIeb0lZXj+UArA==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + nopt@8.1.0: + resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-bundled@4.0.0: + resolution: {integrity: sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==} + engines: {node: ^18.17.0 || >=20.5.0} + + npm-install-checks@7.1.1: + resolution: {integrity: sha512-u6DCwbow5ynAX5BdiHQ9qvexme4U3qHW3MWe5NqH+NeBm0LbiH6zvGjNNew1fY+AZZUtVHbOPF3j7mJxbUzpXg==} + engines: {node: ^18.17.0 || >=20.5.0} + + npm-normalize-package-bin@4.0.0: + resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} + engines: {node: ^18.17.0 || >=20.5.0} + + npm-package-arg@12.0.2: + resolution: {integrity: sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + npm-packlist@10.0.0: + resolution: {integrity: sha512-rht9U6nS8WOBDc53eipZNPo5qkAV4X2rhKE2Oj1DYUQ3DieXfj0mKkVmjnf3iuNdtMd8WfLdi2L6ASkD/8a+Kg==} + engines: {node: ^20.17.0 || >=22.9.0} + + npm-pick-manifest@10.0.0: + resolution: {integrity: sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + npm-registry-fetch@18.0.2: + resolution: {integrity: sha512-LeVMZBBVy+oQb5R6FDV9OlJCcWDU+al10oKpe+nsvcHnG24Z3uM3SvJYKfGJlfGjVU8v9liejCrUR/M5HO5NEQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + ora@8.2.0: + resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} + engines: {node: '>=18'} + + ordered-binary@1.5.3: + resolution: {integrity: sha512-oGFr3T+pYdTGJ+YFEILMpS3es+GiIbs9h/XQrclBXUtd44ey7XwfsMzM31f64I1SQOawDoDr/D823kNCADI8TA==} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + p-map@7.0.3: + resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==} + engines: {node: '>=18'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + pacote@21.0.0: + resolution: {integrity: sha512-lcqexq73AMv6QNLo7SOpz0JJoaGdS3rBFgF122NZVl1bApo2mfu+XzUBU/X/XsiJu+iUmKpekRayqQYAs+PhkA==} + engines: {node: ^20.17.0 || >=22.9.0} + hasBin: true + + parse5-html-rewriting-stream@7.1.0: + resolution: {integrity: sha512-2ifK6Jb+ONoqOy5f+cYHsqvx1obHQdvIk13Jmt/5ezxP0U9p+fqd+R6O73KblGswyuzBYfetmsfK9ThMgnuPPg==} + + parse5-sax-parser@7.0.0: + resolution: {integrity: sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + piscina@5.0.0: + resolution: {integrity: sha512-R+arufwL7sZvGjAhSMK3TfH55YdGOqhpKXkcwQJr432AAnJX/xxX19PA4QisrmJ+BTTfZVggaz6HexbkQq1l1Q==} + engines: {node: '>=18.x'} + + postcss-media-query-parser@0.2.3: + resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} + + postcss@8.5.5: + resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==} + engines: {node: ^10 || ^12 || >=14} + + proc-log@5.0.0: + resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + + punycode@1.4.1: + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} + + qjobs@1.2.0: + resolution: {integrity: sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==} + engines: {node: '>=0.9'} + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup@4.40.2: + resolution: {integrity: sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sass@1.88.0: + resolution: {integrity: sha512-sF6TWQqjFvr4JILXzG4ucGOLELkESHL+I5QJhh7CNaE+Yge0SI+ehCatsXhJ7ymU1hAFcIS3/PBpjdIbXoyVbg==} + engines: {node: '>=14.0.0'} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sigstore@3.1.0: + resolution: {integrity: sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==} + engines: {node: ^18.17.0 || >=20.5.0} + + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + slice-ansi@7.1.0: + resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} + engines: {node: '>=18'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socket.io-adapter@2.5.5: + resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} + + socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + + socket.io@4.8.1: + resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} + engines: {node: '>=10.2.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.5: + resolution: {integrity: sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.21: + resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + ssri@12.0.0: + resolution: {integrity: sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + + streamroller@3.1.5: + resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==} + engines: {node: '>=8.0'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + + tinyglobby@0.2.13: + resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} + engines: {node: '>=12.0.0'} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + tmp@0.2.3: + resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + engines: {node: '>=14.14'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tuf-js@3.0.1: + resolution: {integrity: sha512-+68OP1ZzSF84rTckf3FA95vJ1Zlx/uaXyiiKyPd1pA4rZNkpEvDAKmsu1xUSmbF/chCRYgZ6UZkDwC7PmzmAyA==} + engines: {node: ^18.17.0 || >=20.5.0} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + ua-parser-js@0.7.40: + resolution: {integrity: sha512-us1E3K+3jJppDBa3Tl0L3MOJiGhe1C6P0+nIvQAFYbxlMAx0h81eOwLmU57xgqToduDDPx3y5QsdjPfDu+FgOQ==} + hasBin: true + + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + + unique-filename@4.0.0: + resolution: {integrity: sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==} + engines: {node: ^18.17.0 || >=20.5.0} + + unique-slug@5.0.0: + resolution: {integrity: sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==} + engines: {node: ^18.17.0 || >=20.5.0} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + validate-npm-package-name@6.0.1: + resolution: {integrity: sha512-OaI//3H0J7ZkR1OqlhGA8cA+Cbk/2xFOQpJOt5+s27/ta9eZwpeervh4Mxh4w0im/kdgktowaqVNR7QOrUd7Yg==} + engines: {node: ^18.17.0 || >=20.5.0} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite@6.3.5: + resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + void-elements@2.0.1: + resolution: {integrity: sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==} + engines: {node: '>=0.10.0'} + + watchpack@2.4.2: + resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} + engines: {node: '>=10.13.0'} + + weak-lru-cache@1.2.2: + resolution: {integrity: sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==} + + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@5.0.0: + resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrap-ansi@9.0.0: + resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + + zone.js@0.15.1: + resolution: {integrity: sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@angular-devkit/architect@0.2000.2(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 20.0.2(chokidar@4.0.3) + rxjs: 7.8.2 + transitivePeerDependencies: + - chokidar + + '@angular-devkit/core@20.0.2(chokidar@4.0.3)': + dependencies: + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + jsonc-parser: 3.3.1 + picomatch: 4.0.2 + rxjs: 7.8.2 + source-map: 0.7.4 + optionalDependencies: + chokidar: 4.0.3 + + '@angular-devkit/schematics@20.0.2(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 20.0.2(chokidar@4.0.3) + jsonc-parser: 3.3.1 + magic-string: 0.30.17 + ora: 8.2.0 + rxjs: 7.8.2 + transitivePeerDependencies: + - chokidar + + '@angular/build@20.0.2(@angular/compiler-cli@20.0.3(@angular/compiler@20.0.3)(typescript@5.8.3))(@angular/compiler@20.0.3)(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.0.1)(chokidar@4.0.3)(karma@6.4.4)(postcss@8.5.5)(tslib@2.8.1)(typescript@5.8.3)': + dependencies: + '@ampproject/remapping': 2.3.0 + '@angular-devkit/architect': 0.2000.2(chokidar@4.0.3) + '@angular/compiler': 20.0.3 + '@angular/compiler-cli': 20.0.3(@angular/compiler@20.0.3)(typescript@5.8.3) + '@babel/core': 7.27.1 + '@babel/helper-annotate-as-pure': 7.27.1 + '@babel/helper-split-export-declaration': 7.24.7 + '@inquirer/confirm': 5.1.10(@types/node@24.0.1) + '@vitejs/plugin-basic-ssl': 2.0.0(vite@6.3.5(@types/node@24.0.1)(sass@1.88.0)) + beasties: 0.3.4 + browserslist: 4.25.0 + esbuild: 0.25.5 + https-proxy-agent: 7.0.6 + istanbul-lib-instrument: 6.0.3 + jsonc-parser: 3.3.1 + listr2: 8.3.3 + magic-string: 0.30.17 + mrmime: 2.0.1 + parse5-html-rewriting-stream: 7.1.0 + picomatch: 4.0.2 + piscina: 5.0.0 + rollup: 4.40.2 + sass: 1.88.0 + semver: 7.7.2 + source-map-support: 0.5.21 + tinyglobby: 0.2.13 + tslib: 2.8.1 + typescript: 5.8.3 + vite: 6.3.5(@types/node@24.0.1)(sass@1.88.0) + watchpack: 2.4.2 + optionalDependencies: + '@angular/core': 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/platform-browser': 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)) + karma: 6.4.4 + lmdb: 3.3.0 + postcss: 8.5.5 + transitivePeerDependencies: + - '@types/node' + - chokidar + - jiti + - lightningcss + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + '@angular/cli@20.0.2(@types/node@24.0.1)(chokidar@4.0.3)': + dependencies: + '@angular-devkit/architect': 0.2000.2(chokidar@4.0.3) + '@angular-devkit/core': 20.0.2(chokidar@4.0.3) + '@angular-devkit/schematics': 20.0.2(chokidar@4.0.3) + '@inquirer/prompts': 7.5.1(@types/node@24.0.1) + '@listr2/prompt-adapter-inquirer': 2.0.22(@inquirer/prompts@7.5.1(@types/node@24.0.1)) + '@schematics/angular': 20.0.2(chokidar@4.0.3) + '@yarnpkg/lockfile': 1.1.0 + ini: 5.0.0 + jsonc-parser: 3.3.1 + listr2: 8.3.3 + npm-package-arg: 12.0.2 + npm-pick-manifest: 10.0.0 + pacote: 21.0.0 + resolve: 1.22.10 + semver: 7.7.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - chokidar + - supports-color + + '@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)': + dependencies: + '@angular/core': 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) + rxjs: 7.8.2 + tslib: 2.8.1 + + '@angular/compiler-cli@20.0.3(@angular/compiler@20.0.3)(typescript@5.8.3)': + dependencies: + '@angular/compiler': 20.0.3 + '@babel/core': 7.27.4 + '@jridgewell/sourcemap-codec': 1.5.0 + chokidar: 4.0.3 + convert-source-map: 1.9.0 + reflect-metadata: 0.2.2 + semver: 7.7.2 + tslib: 2.8.1 + yargs: 18.0.0 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@angular/compiler@20.0.3': + dependencies: + tslib: 2.8.1 + + '@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)': + dependencies: + rxjs: 7.8.2 + tslib: 2.8.1 + optionalDependencies: + '@angular/compiler': 20.0.3 + zone.js: 0.15.1 + + '@angular/forms@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': + dependencies: + '@angular/common': 20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/platform-browser': 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)) + rxjs: 7.8.2 + tslib: 2.8.1 + + '@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))': + dependencies: + '@angular/common': 20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) + tslib: 2.8.1 + + '@angular/router@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': + dependencies: + '@angular/common': 20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/platform-browser': 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)) + rxjs: 7.8.2 + tslib: 2.8.1 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.27.5': {} + + '@babel/core@7.27.1': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.1) + '@babel/helpers': 7.27.6 + '@babel/parser': 7.27.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/core@7.27.4': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4) + '@babel/helpers': 7.27.6 + '@babel/parser': 7.27.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.27.5': + dependencies: + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.1': + dependencies: + '@babel/types': 7.27.6 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.27.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.1)': + dependencies: + '@babel/core': 7.27.1 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-split-export-declaration@7.24.7': + dependencies: + '@babel/types': 7.27.6 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.27.6': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.27.6 + + '@babel/parser@7.27.5': + dependencies: + '@babel/types': 7.27.6 + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + + '@babel/traverse@7.27.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.5 + '@babel/parser': 7.27.5 + '@babel/template': 7.27.2 + '@babel/types': 7.27.6 + debug: 4.4.1 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.27.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@colors/colors@1.5.0': {} + + '@esbuild/aix-ppc64@0.25.5': + optional: true + + '@esbuild/android-arm64@0.25.5': + optional: true + + '@esbuild/android-arm@0.25.5': + optional: true + + '@esbuild/android-x64@0.25.5': + optional: true + + '@esbuild/darwin-arm64@0.25.5': + optional: true + + '@esbuild/darwin-x64@0.25.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.5': + optional: true + + '@esbuild/freebsd-x64@0.25.5': + optional: true + + '@esbuild/linux-arm64@0.25.5': + optional: true + + '@esbuild/linux-arm@0.25.5': + optional: true + + '@esbuild/linux-ia32@0.25.5': + optional: true + + '@esbuild/linux-loong64@0.25.5': + optional: true + + '@esbuild/linux-mips64el@0.25.5': + optional: true + + '@esbuild/linux-ppc64@0.25.5': + optional: true + + '@esbuild/linux-riscv64@0.25.5': + optional: true + + '@esbuild/linux-s390x@0.25.5': + optional: true + + '@esbuild/linux-x64@0.25.5': + optional: true + + '@esbuild/netbsd-arm64@0.25.5': + optional: true + + '@esbuild/netbsd-x64@0.25.5': + optional: true + + '@esbuild/openbsd-arm64@0.25.5': + optional: true + + '@esbuild/openbsd-x64@0.25.5': + optional: true + + '@esbuild/sunos-x64@0.25.5': + optional: true + + '@esbuild/win32-arm64@0.25.5': + optional: true + + '@esbuild/win32-ia32@0.25.5': + optional: true + + '@esbuild/win32-x64@0.25.5': + optional: true + + '@inquirer/checkbox@4.1.8(@types/node@24.0.1)': + dependencies: + '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/figures': 1.0.12 + '@inquirer/type': 3.0.7(@types/node@24.0.1) + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/confirm@5.1.10(@types/node@24.0.1)': + dependencies: + '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/type': 3.0.7(@types/node@24.0.1) + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/confirm@5.1.12(@types/node@24.0.1)': + dependencies: + '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/type': 3.0.7(@types/node@24.0.1) + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/core@10.1.13(@types/node@24.0.1)': + dependencies: + '@inquirer/figures': 1.0.12 + '@inquirer/type': 3.0.7(@types/node@24.0.1) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/editor@4.2.13(@types/node@24.0.1)': + dependencies: + '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/type': 3.0.7(@types/node@24.0.1) + external-editor: 3.1.0 + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/expand@4.0.15(@types/node@24.0.1)': + dependencies: + '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/type': 3.0.7(@types/node@24.0.1) + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/figures@1.0.12': {} + + '@inquirer/input@4.1.12(@types/node@24.0.1)': + dependencies: + '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/type': 3.0.7(@types/node@24.0.1) + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/number@3.0.15(@types/node@24.0.1)': + dependencies: + '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/type': 3.0.7(@types/node@24.0.1) + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/password@4.0.15(@types/node@24.0.1)': + dependencies: + '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/type': 3.0.7(@types/node@24.0.1) + ansi-escapes: 4.3.2 + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/prompts@7.5.1(@types/node@24.0.1)': + dependencies: + '@inquirer/checkbox': 4.1.8(@types/node@24.0.1) + '@inquirer/confirm': 5.1.12(@types/node@24.0.1) + '@inquirer/editor': 4.2.13(@types/node@24.0.1) + '@inquirer/expand': 4.0.15(@types/node@24.0.1) + '@inquirer/input': 4.1.12(@types/node@24.0.1) + '@inquirer/number': 3.0.15(@types/node@24.0.1) + '@inquirer/password': 4.0.15(@types/node@24.0.1) + '@inquirer/rawlist': 4.1.3(@types/node@24.0.1) + '@inquirer/search': 3.0.15(@types/node@24.0.1) + '@inquirer/select': 4.2.3(@types/node@24.0.1) + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/rawlist@4.1.3(@types/node@24.0.1)': + dependencies: + '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/type': 3.0.7(@types/node@24.0.1) + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/search@3.0.15(@types/node@24.0.1)': + dependencies: + '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/figures': 1.0.12 + '@inquirer/type': 3.0.7(@types/node@24.0.1) + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/select@4.2.3(@types/node@24.0.1)': + dependencies: + '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/figures': 1.0.12 + '@inquirer/type': 3.0.7(@types/node@24.0.1) + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 24.0.1 + + '@inquirer/type@1.5.5': + dependencies: + mute-stream: 1.0.0 + + '@inquirer/type@3.0.7(@types/node@24.0.1)': + optionalDependencies: + '@types/node': 24.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@istanbuljs/schema@0.1.3': {} + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@listr2/prompt-adapter-inquirer@2.0.22(@inquirer/prompts@7.5.1(@types/node@24.0.1))': + dependencies: + '@inquirer/prompts': 7.5.1(@types/node@24.0.1) + '@inquirer/type': 1.5.5 + + '@lmdb/lmdb-darwin-arm64@3.3.0': + optional: true + + '@lmdb/lmdb-darwin-x64@3.3.0': + optional: true + + '@lmdb/lmdb-linux-arm64@3.3.0': + optional: true + + '@lmdb/lmdb-linux-arm@3.3.0': + optional: true + + '@lmdb/lmdb-linux-x64@3.3.0': + optional: true + + '@lmdb/lmdb-win32-arm64@3.3.0': + optional: true + + '@lmdb/lmdb-win32-x64@3.3.0': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + optional: true + + '@napi-rs/nice-android-arm-eabi@1.0.1': + optional: true + + '@napi-rs/nice-android-arm64@1.0.1': + optional: true + + '@napi-rs/nice-darwin-arm64@1.0.1': + optional: true + + '@napi-rs/nice-darwin-x64@1.0.1': + optional: true + + '@napi-rs/nice-freebsd-x64@1.0.1': + optional: true + + '@napi-rs/nice-linux-arm-gnueabihf@1.0.1': + optional: true + + '@napi-rs/nice-linux-arm64-gnu@1.0.1': + optional: true + + '@napi-rs/nice-linux-arm64-musl@1.0.1': + optional: true + + '@napi-rs/nice-linux-ppc64-gnu@1.0.1': + optional: true + + '@napi-rs/nice-linux-riscv64-gnu@1.0.1': + optional: true + + '@napi-rs/nice-linux-s390x-gnu@1.0.1': + optional: true + + '@napi-rs/nice-linux-x64-gnu@1.0.1': + optional: true + + '@napi-rs/nice-linux-x64-musl@1.0.1': + optional: true + + '@napi-rs/nice-win32-arm64-msvc@1.0.1': + optional: true + + '@napi-rs/nice-win32-ia32-msvc@1.0.1': + optional: true + + '@napi-rs/nice-win32-x64-msvc@1.0.1': + optional: true + + '@napi-rs/nice@1.0.1': + optionalDependencies: + '@napi-rs/nice-android-arm-eabi': 1.0.1 + '@napi-rs/nice-android-arm64': 1.0.1 + '@napi-rs/nice-darwin-arm64': 1.0.1 + '@napi-rs/nice-darwin-x64': 1.0.1 + '@napi-rs/nice-freebsd-x64': 1.0.1 + '@napi-rs/nice-linux-arm-gnueabihf': 1.0.1 + '@napi-rs/nice-linux-arm64-gnu': 1.0.1 + '@napi-rs/nice-linux-arm64-musl': 1.0.1 + '@napi-rs/nice-linux-ppc64-gnu': 1.0.1 + '@napi-rs/nice-linux-riscv64-gnu': 1.0.1 + '@napi-rs/nice-linux-s390x-gnu': 1.0.1 + '@napi-rs/nice-linux-x64-gnu': 1.0.1 + '@napi-rs/nice-linux-x64-musl': 1.0.1 + '@napi-rs/nice-win32-arm64-msvc': 1.0.1 + '@napi-rs/nice-win32-ia32-msvc': 1.0.1 + '@napi-rs/nice-win32-x64-msvc': 1.0.1 + optional: true + + '@npmcli/agent@3.0.0': + dependencies: + agent-base: 7.1.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 10.4.3 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + '@npmcli/fs@4.0.0': + dependencies: + semver: 7.7.2 + + '@npmcli/git@6.0.3': + dependencies: + '@npmcli/promise-spawn': 8.0.2 + ini: 5.0.0 + lru-cache: 10.4.3 + npm-pick-manifest: 10.0.0 + proc-log: 5.0.0 + promise-retry: 2.0.1 + semver: 7.7.2 + which: 5.0.0 + + '@npmcli/installed-package-contents@3.0.0': + dependencies: + npm-bundled: 4.0.0 + npm-normalize-package-bin: 4.0.0 + + '@npmcli/node-gyp@4.0.0': {} + + '@npmcli/package-json@6.2.0': + dependencies: + '@npmcli/git': 6.0.3 + glob: 10.4.5 + hosted-git-info: 8.1.0 + json-parse-even-better-errors: 4.0.0 + proc-log: 5.0.0 + semver: 7.7.2 + validate-npm-package-license: 3.0.4 + + '@npmcli/promise-spawn@8.0.2': + dependencies: + which: 5.0.0 + + '@npmcli/redact@3.2.2': {} + + '@npmcli/run-script@9.1.0': + dependencies: + '@npmcli/node-gyp': 4.0.0 + '@npmcli/package-json': 6.2.0 + '@npmcli/promise-spawn': 8.0.2 + node-gyp: 11.2.0 + proc-log: 5.0.0 + which: 5.0.0 + transitivePeerDependencies: + - supports-color + + '@parcel/watcher-android-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + optional: true + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@rollup/rollup-android-arm-eabi@4.40.2': + optional: true + + '@rollup/rollup-android-arm64@4.40.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.40.2': + optional: true + + '@rollup/rollup-darwin-x64@4.40.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.40.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.40.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.40.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.40.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.40.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.40.2': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.40.2': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.40.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.40.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.40.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.40.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.40.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.40.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.40.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.40.2': + optional: true + + '@schematics/angular@20.0.2(chokidar@4.0.3)': + dependencies: + '@angular-devkit/core': 20.0.2(chokidar@4.0.3) + '@angular-devkit/schematics': 20.0.2(chokidar@4.0.3) + jsonc-parser: 3.3.1 + transitivePeerDependencies: + - chokidar + + '@sigstore/bundle@3.1.0': + dependencies: + '@sigstore/protobuf-specs': 0.4.3 + + '@sigstore/core@2.0.0': {} + + '@sigstore/protobuf-specs@0.4.3': {} + + '@sigstore/sign@3.1.0': + dependencies: + '@sigstore/bundle': 3.1.0 + '@sigstore/core': 2.0.0 + '@sigstore/protobuf-specs': 0.4.3 + make-fetch-happen: 14.0.3 + proc-log: 5.0.0 + promise-retry: 2.0.1 + transitivePeerDependencies: + - supports-color + + '@sigstore/tuf@3.1.1': + dependencies: + '@sigstore/protobuf-specs': 0.4.3 + tuf-js: 3.0.1 + transitivePeerDependencies: + - supports-color + + '@sigstore/verify@2.1.1': + dependencies: + '@sigstore/bundle': 3.1.0 + '@sigstore/core': 2.0.0 + '@sigstore/protobuf-specs': 0.4.3 + + '@socket.io/component-emitter@3.1.2': {} + + '@tufjs/canonical-json@2.0.0': {} + + '@tufjs/models@3.0.1': + dependencies: + '@tufjs/canonical-json': 2.0.0 + minimatch: 9.0.5 + + '@types/cors@2.8.19': + dependencies: + '@types/node': 24.0.1 + + '@types/estree@1.0.7': {} + + '@types/jasmine@5.1.8': {} + + '@types/node@24.0.1': + dependencies: + undici-types: 7.8.0 + + '@vitejs/plugin-basic-ssl@2.0.0(vite@6.3.5(@types/node@24.0.1)(sass@1.88.0))': + dependencies: + vite: 6.3.5(@types/node@24.0.1)(sass@1.88.0) + + '@yarnpkg/lockfile@1.1.0': {} + + abbrev@3.0.1: {} + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + agent-base@7.1.3: {} + + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.0.6 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-escapes@7.0.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.1: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + balanced-match@1.0.2: {} + + base64id@2.0.0: {} + + beasties@0.3.4: + dependencies: + css-select: 5.1.0 + css-what: 6.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + htmlparser2: 10.0.0 + picocolors: 1.1.1 + postcss: 8.5.5 + postcss-media-query-parser: 0.2.3 + + binary-extensions@2.3.0: {} + + body-parser@1.20.3: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.25.0: + dependencies: + caniuse-lite: 1.0.30001723 + electron-to-chromium: 1.5.167 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.0) + + buffer-from@1.1.2: {} + + bytes@3.1.2: {} + + cacache@19.0.1: + dependencies: + '@npmcli/fs': 4.0.0 + fs-minipass: 3.0.3 + glob: 10.4.5 + lru-cache: 10.4.3 + minipass: 7.1.2 + minipass-collect: 2.0.1 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + p-map: 7.0.3 + ssri: 12.0.0 + tar: 7.4.3 + unique-filename: 4.0.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + caniuse-lite@1.0.30001723: {} + + chalk@5.4.1: {} + + chardet@0.7.0: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chownr@2.0.0: {} + + chownr@3.0.0: {} + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + cli-width@4.1.0: {} + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + concat-map@0.0.1: {} + + connect@3.7.0: + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + + content-type@1.0.5: {} + + convert-source-map@1.9.0: {} + + convert-source-map@2.0.0: {} + + cookie@0.7.2: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-select@5.1.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-what@6.1.0: {} + + custom-event@1.0.1: {} + + date-format@4.0.14: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.3.7: + dependencies: + ms: 2.1.3 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + depd@2.0.0: {} + + destroy@1.2.0: {} + + detect-libc@1.0.3: + optional: true + + detect-libc@2.0.4: + optional: true + + di@0.0.1: {} + + dom-serialize@2.2.1: + dependencies: + custom-event: 1.0.1 + ent: 2.2.2 + extend: 3.0.2 + void-elements: 2.0.1 + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.167: {} + + emoji-regex@10.4.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@1.0.2: {} + + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + + engine.io-parser@5.2.3: {} + + engine.io@6.6.4: + dependencies: + '@types/cors': 2.8.19 + '@types/node': 24.0.1 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.7.2 + cors: 2.8.5 + debug: 4.3.7 + engine.io-parser: 5.2.3 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + ent@2.2.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + punycode: 1.4.1 + safe-regex-test: 1.1.0 + + entities@4.5.0: {} + + entities@6.0.1: {} + + env-paths@2.2.1: {} + + environment@1.1.0: {} + + err-code@2.0.3: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + esbuild@0.25.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + eventemitter3@4.0.7: {} + + eventemitter3@5.0.1: {} + + exponential-backoff@3.1.2: {} + + extend@3.0.2: {} + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + fast-deep-equal@3.1.3: {} + + fast-uri@3.0.6: {} + + fdir@6.4.6(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.1.2: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + flatted@3.3.3: {} + + follow-redirects@1.15.9: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs-minipass@3.0.3: + dependencies: + minipass: 7.1.2 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.3.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-to-regexp@0.4.1: {} + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@11.12.0: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hosted-git-info@8.1.0: + dependencies: + lru-cache: 10.4.3 + + html-escaper@2.0.2: {} + + htmlparser2@10.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 6.0.1 + + http-cache-semantics@4.2.0: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.3 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + http-proxy@1.18.1: + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.9 + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + + ignore-walk@7.0.0: + dependencies: + minimatch: 9.0.5 + + immutable@5.1.3: {} + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@5.0.0: {} + + ip-address@9.0.5: + dependencies: + jsbn: 1.1.0 + sprintf-js: 1.1.3 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.0.0: + dependencies: + get-east-asian-width: 1.3.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-interactive@2.0.0: {} + + is-number@7.0.0: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + + isbinaryfile@4.0.10: {} + + isexe@2.0.0: {} + + isexe@3.1.1: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.27.4 + '@babel/parser': 7.27.5 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.27.1 + '@babel/parser': 7.27.5 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jasmine-core@4.6.1: {} + + jasmine-core@5.7.1: {} + + js-tokens@4.0.0: {} + + jsbn@1.1.0: {} + + jsesc@3.1.0: {} + + json-parse-even-better-errors@4.0.0: {} + + json-schema-traverse@1.0.0: {} + + json5@2.2.3: {} + + jsonc-parser@3.3.1: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonparse@1.3.1: {} + + karma-chrome-launcher@3.2.0: + dependencies: + which: 1.3.1 + + karma-coverage@2.2.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 5.2.1 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + karma-jasmine-html-reporter@2.1.0(jasmine-core@5.7.1)(karma-jasmine@5.1.0(karma@6.4.4))(karma@6.4.4): + dependencies: + jasmine-core: 5.7.1 + karma: 6.4.4 + karma-jasmine: 5.1.0(karma@6.4.4) + + karma-jasmine@5.1.0(karma@6.4.4): + dependencies: + jasmine-core: 4.6.1 + karma: 6.4.4 + + karma@6.4.4: + dependencies: + '@colors/colors': 1.5.0 + body-parser: 1.20.3 + braces: 3.0.3 + chokidar: 3.6.0 + connect: 3.7.0 + di: 0.0.1 + dom-serialize: 2.2.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + http-proxy: 1.18.1 + isbinaryfile: 4.0.10 + lodash: 4.17.21 + log4js: 6.9.1 + mime: 2.6.0 + minimatch: 3.1.2 + mkdirp: 0.5.6 + qjobs: 1.2.0 + range-parser: 1.2.1 + rimraf: 3.0.2 + socket.io: 4.8.1 + source-map: 0.6.1 + tmp: 0.2.3 + ua-parser-js: 0.7.40 + yargs: 16.2.0 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + listr2@8.3.3: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.0 + + lmdb@3.3.0: + dependencies: + msgpackr: 1.11.4 + node-addon-api: 6.1.0 + node-gyp-build-optional-packages: 5.2.2 + ordered-binary: 1.5.3 + weak-lru-cache: 1.2.2 + optionalDependencies: + '@lmdb/lmdb-darwin-arm64': 3.3.0 + '@lmdb/lmdb-darwin-x64': 3.3.0 + '@lmdb/lmdb-linux-arm': 3.3.0 + '@lmdb/lmdb-linux-arm64': 3.3.0 + '@lmdb/lmdb-linux-x64': 3.3.0 + '@lmdb/lmdb-win32-arm64': 3.3.0 + '@lmdb/lmdb-win32-x64': 3.3.0 + optional: true + + lodash@4.17.21: {} + + log-symbols@6.0.0: + dependencies: + chalk: 5.4.1 + is-unicode-supported: 1.3.0 + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.0.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + + log4js@6.9.1: + dependencies: + date-format: 4.0.14 + debug: 4.4.1 + flatted: 3.3.3 + rfdc: 1.4.1 + streamroller: 3.1.5 + transitivePeerDependencies: + - supports-color + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + make-dir@4.0.0: + dependencies: + semver: 7.7.2 + + make-fetch-happen@14.0.3: + dependencies: + '@npmcli/agent': 3.0.0 + cacache: 19.0.1 + http-cache-semantics: 4.2.0 + minipass: 7.1.2 + minipass-fetch: 4.0.1 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 1.0.0 + proc-log: 5.0.0 + promise-retry: 2.0.1 + ssri: 12.0.0 + transitivePeerDependencies: + - supports-color + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + optional: true + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@2.6.0: {} + + mimic-function@5.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass-collect@2.0.1: + dependencies: + minipass: 7.1.2 + + minipass-fetch@4.0.1: + dependencies: + minipass: 7.1.2 + minipass-sized: 1.0.3 + minizlib: 3.0.2 + optionalDependencies: + encoding: 0.1.13 + + minipass-flush@1.0.5: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@1.0.3: + dependencies: + minipass: 3.3.6 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.2: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mkdirp@1.0.4: {} + + mkdirp@3.0.1: {} + + mrmime@2.0.1: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + msgpackr-extract@3.0.3: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 + optional: true + + msgpackr@1.11.4: + optionalDependencies: + msgpackr-extract: 3.0.3 + optional: true + + mute-stream@1.0.0: {} + + mute-stream@2.0.0: {} + + nanoid@3.3.11: {} + + negotiator@0.6.3: {} + + negotiator@1.0.0: {} + + node-addon-api@6.1.0: + optional: true + + node-addon-api@7.1.1: + optional: true + + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.0.4 + optional: true + + node-gyp@11.2.0: + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.2 + graceful-fs: 4.2.11 + make-fetch-happen: 14.0.3 + nopt: 8.1.0 + proc-log: 5.0.0 + semver: 7.7.2 + tar: 7.4.3 + tinyglobby: 0.2.14 + which: 5.0.0 + transitivePeerDependencies: + - supports-color + + node-releases@2.0.19: {} + + nopt@8.1.0: + dependencies: + abbrev: 3.0.1 + + normalize-path@3.0.0: {} + + npm-bundled@4.0.0: + dependencies: + npm-normalize-package-bin: 4.0.0 + + npm-install-checks@7.1.1: + dependencies: + semver: 7.7.2 + + npm-normalize-package-bin@4.0.0: {} + + npm-package-arg@12.0.2: + dependencies: + hosted-git-info: 8.1.0 + proc-log: 5.0.0 + semver: 7.7.2 + validate-npm-package-name: 6.0.1 + + npm-packlist@10.0.0: + dependencies: + ignore-walk: 7.0.0 + + npm-pick-manifest@10.0.0: + dependencies: + npm-install-checks: 7.1.1 + npm-normalize-package-bin: 4.0.0 + npm-package-arg: 12.0.2 + semver: 7.7.2 + + npm-registry-fetch@18.0.2: + dependencies: + '@npmcli/redact': 3.2.2 + jsonparse: 1.3.1 + make-fetch-happen: 14.0.3 + minipass: 7.1.2 + minipass-fetch: 4.0.1 + minizlib: 3.0.2 + npm-package-arg: 12.0.2 + proc-log: 5.0.0 + transitivePeerDependencies: + - supports-color + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + ora@8.2.0: + dependencies: + chalk: 5.4.1 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.1.0 + + ordered-binary@1.5.3: + optional: true + + os-tmpdir@1.0.2: {} + + p-map@7.0.3: {} + + package-json-from-dist@1.0.1: {} + + pacote@21.0.0: + dependencies: + '@npmcli/git': 6.0.3 + '@npmcli/installed-package-contents': 3.0.0 + '@npmcli/package-json': 6.2.0 + '@npmcli/promise-spawn': 8.0.2 + '@npmcli/run-script': 9.1.0 + cacache: 19.0.1 + fs-minipass: 3.0.3 + minipass: 7.1.2 + npm-package-arg: 12.0.2 + npm-packlist: 10.0.0 + npm-pick-manifest: 10.0.0 + npm-registry-fetch: 18.0.2 + proc-log: 5.0.0 + promise-retry: 2.0.1 + sigstore: 3.1.0 + ssri: 12.0.0 + tar: 6.2.1 + transitivePeerDependencies: + - supports-color + + parse5-html-rewriting-stream@7.1.0: + dependencies: + entities: 6.0.1 + parse5: 7.3.0 + parse5-sax-parser: 7.0.0 + + parse5-sax-parser@7.0.0: + dependencies: + parse5: 7.3.0 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + parseurl@1.3.3: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + piscina@5.0.0: + optionalDependencies: + '@napi-rs/nice': 1.0.1 + + postcss-media-query-parser@0.2.3: {} + + postcss@8.5.5: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + proc-log@5.0.0: {} + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + + punycode@1.4.1: {} + + qjobs@1.2.0: {} + + qs@6.13.0: + dependencies: + side-channel: 1.1.0 + + range-parser@1.2.1: {} + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@4.1.2: {} + + reflect-metadata@0.2.2: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + requires-port@1.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + retry@0.12.0: {} + + rfdc@1.4.1: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup@4.40.2: + dependencies: + '@types/estree': 1.0.7 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.40.2 + '@rollup/rollup-android-arm64': 4.40.2 + '@rollup/rollup-darwin-arm64': 4.40.2 + '@rollup/rollup-darwin-x64': 4.40.2 + '@rollup/rollup-freebsd-arm64': 4.40.2 + '@rollup/rollup-freebsd-x64': 4.40.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.40.2 + '@rollup/rollup-linux-arm-musleabihf': 4.40.2 + '@rollup/rollup-linux-arm64-gnu': 4.40.2 + '@rollup/rollup-linux-arm64-musl': 4.40.2 + '@rollup/rollup-linux-loongarch64-gnu': 4.40.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.40.2 + '@rollup/rollup-linux-riscv64-gnu': 4.40.2 + '@rollup/rollup-linux-riscv64-musl': 4.40.2 + '@rollup/rollup-linux-s390x-gnu': 4.40.2 + '@rollup/rollup-linux-x64-gnu': 4.40.2 + '@rollup/rollup-linux-x64-musl': 4.40.2 + '@rollup/rollup-win32-arm64-msvc': 4.40.2 + '@rollup/rollup-win32-ia32-msvc': 4.40.2 + '@rollup/rollup-win32-x64-msvc': 4.40.2 + fsevents: 2.3.3 + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + sass@1.88.0: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.3 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.1 + + semver@6.3.1: {} + + semver@7.7.2: {} + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + sigstore@3.1.0: + dependencies: + '@sigstore/bundle': 3.1.0 + '@sigstore/core': 2.0.0 + '@sigstore/protobuf-specs': 0.4.3 + '@sigstore/sign': 3.1.0 + '@sigstore/tuf': 3.1.1 + '@sigstore/verify': 2.1.1 + transitivePeerDependencies: + - supports-color + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.0.0 + + smart-buffer@4.2.0: {} + + socket.io-adapter@2.5.5: + dependencies: + debug: 4.3.7 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + socket.io@4.8.1: + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.5 + debug: 4.3.7 + engine.io: 6.6.4 + socket.io-adapter: 2.5.5 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.3 + debug: 4.4.1 + socks: 2.8.5 + transitivePeerDependencies: + - supports-color + + socks@2.8.5: + dependencies: + ip-address: 9.0.5 + smart-buffer: 4.2.0 + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.4: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.21 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.21 + + spdx-license-ids@3.0.21: {} + + sprintf-js@1.1.3: {} + + ssri@12.0.0: + dependencies: + minipass: 7.1.2 + + statuses@1.5.0: {} + + statuses@2.0.1: {} + + stdin-discarder@0.2.2: {} + + streamroller@3.1.5: + dependencies: + date-format: 4.0.14 + debug: 4.4.1 + fs-extra: 8.1.0 + transitivePeerDependencies: + - supports-color + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.3.0 + strip-ansi: 7.1.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + tar@7.4.3: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + + tinyglobby@0.2.13: + dependencies: + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + tmp@0.2.3: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tslib@2.8.1: {} + + tuf-js@3.0.1: + dependencies: + '@tufjs/models': 3.0.1 + debug: 4.4.1 + make-fetch-happen: 14.0.3 + transitivePeerDependencies: + - supports-color + + type-fest@0.21.3: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typescript@5.8.3: {} + + ua-parser-js@0.7.40: {} + + undici-types@7.8.0: {} + + unique-filename@4.0.0: + dependencies: + unique-slug: 5.0.0 + + unique-slug@5.0.0: + dependencies: + imurmurhash: 0.1.4 + + universalify@0.1.2: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.1.3(browserslist@4.25.0): + dependencies: + browserslist: 4.25.0 + escalade: 3.2.0 + picocolors: 1.1.1 + + utils-merge@1.0.1: {} + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + validate-npm-package-name@6.0.1: {} + + vary@1.1.2: {} + + vite@6.3.5(@types/node@24.0.1)(sass@1.88.0): + dependencies: + esbuild: 0.25.5 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.5 + rollup: 4.40.2 + tinyglobby: 0.2.13 + optionalDependencies: + '@types/node': 24.0.1 + fsevents: 2.3.3 + sass: 1.88.0 + + void-elements@2.0.1: {} + + watchpack@2.4.2: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + weak-lru-cache@1.2.2: + optional: true + + which@1.3.1: + dependencies: + isexe: 2.0.0 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@5.0.0: + dependencies: + isexe: 3.1.1 + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrap-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + ws@8.17.1: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yallist@5.0.0: {} + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs-parser@22.0.0: {} + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + + yoctocolors-cjs@2.1.2: {} + + zone.js@0.15.1: {} diff --git a/src/Vegasco-Web/public/favicon.ico b/src/Vegasco-Web/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..57614f9c967596fad0a3989bec2b1deff33034f6 GIT binary patch literal 15086 zcmd^G33O9Omi+`8$@{|M-I6TH3wzF-p5CV8o}7f~KxR60LK+ApEFB<$bcciv%@SmA zV{n>g85YMFFeU*Uvl=i4v)C*qgnb;$GQ=3XTe9{Y%c`mO%su)noNCCQ*@t1WXn|B(hQ7i~ zrUK8|pUkD6#lNo!bt$6)jR!&C?`P5G(`e((P($RaLeq+o0Vd~f11;qB05kdbAOm?r zXv~GYr_sibQO9NGTCdT;+G(!{4Xs@4fPak8#L8PjgJwcs-Mm#nR_Z0s&u?nDX5^~@ z+A6?}g0|=4e_LoE69pPFO`yCD@BCjgKpzMH0O4Xs{Ahc?K3HC5;l=f zg>}alhBXX&);z$E-wai+9TTRtBX-bWYY@cl$@YN#gMd~tM_5lj6W%8ah4;uZ;jP@Q zVbuel1rPA?2@x9Y+u?e`l{Z4ngfG5q5BLH5QsEu4GVpt{KIp1?U)=3+KQ;%7ec8l* zdV=zZgN5>O3G(3L2fqj3;oBbZZw$Ij@`Juz@?+yy#OPw)>#wsTewVgTK9BGt5AbZ&?K&B3GVF&yu?@(Xj3fR3n+ZP0%+wo)D9_xp>Z$`A4 zfV>}NWjO#3lqumR0`gvnffd9Ka}JJMuHS&|55-*mCD#8e^anA<+sFZVaJe7{=p*oX zE_Uv?1>e~ga=seYzh{9P+n5<+7&9}&(kwqSaz;1aD|YM3HBiy<))4~QJSIryyqp| z8nGc(8>3(_nEI4n)n7j(&d4idW1tVLjZ7QbNLXg;LB ziHsS5pXHEjGJZb59KcvS~wv;uZR-+4qEqow`;JCfB*+b^UL^3!?;-^F%yt=VjU|v z39SSqKcRu_NVvz!zJzL0CceJaS6%!(eMshPv_0U5G`~!a#I$qI5Ic(>IONej@aH=f z)($TAT#1I{iCS4f{D2+ApS=$3E7}5=+y(rA9mM#;Cky%b*Gi0KfFA`ofKTzu`AV-9 znW|y@19rrZ*!N2AvDi<_ZeR3O2R{#dh1#3-d%$k${Rx42h+i&GZo5!C^dSL34*AKp z27mTd>k>?V&X;Nl%GZ(>0s`1UN~Hfyj>KPjtnc|)xM@{H_B9rNr~LuH`Gr5_am&Ep zTjZA8hljNj5H1Ipm-uD9rC}U{-vR!eay5&6x6FkfupdpT*84MVwGpdd(}ib)zZ3Ky z7C$pnjc82(W_y_F{PhYj?o!@3__UUvpX)v69aBSzYj3 zdi}YQkKs^SyXyFG2LTRz9{(w}y~!`{EuAaUr6G1M{*%c+kP1olW9z23dSH!G4_HSK zzae-DF$OGR{ofP*!$a(r^5Go>I3SObVI6FLY)N@o<*gl0&kLo-OT{Tl*7nCz>Iq=? zcigIDHtj|H;6sR?or8Wd_a4996GI*CXGU}o;D9`^FM!AT1pBY~?|4h^61BY#_yIfO zKO?E0 zJ{Pc`9rVEI&$xxXu`<5E)&+m(7zX^v0rqofLs&bnQT(1baQkAr^kEsk)15vlzAZ-l z@OO9RF<+IiJ*O@HE256gCt!bF=NM*vh|WVWmjVawcNoksRTMvR03H{p@cjwKh(CL4 z7_PB(dM=kO)!s4fW!1p0f93YN@?ZSG` z$B!JaAJCtW$B97}HNO9(x-t30&E}Mo1UPi@Av%uHj~?T|!4JLwV;KCx8xO#b9IlUW zI6+{a@Wj|<2Y=U;a@vXbxqZNngH8^}LleE_4*0&O7#3iGxfJ%Id>+sb;7{L=aIic8 z|EW|{{S)J-wr@;3PmlxRXU8!e2gm_%s|ReH!reFcY8%$Hl4M5>;6^UDUUae?kOy#h zk~6Ee_@ZAn48Bab__^bNmQ~+k=02jz)e0d9Z3>G?RGG!65?d1>9}7iG17?P*=GUV-#SbLRw)Hu{zx*azHxWkGNTWl@HeWjA?39Ia|sCi{e;!^`1Oec zb>Z|b65OM*;eC=ZLSy?_fg$&^2xI>qSLA2G*$nA3GEnp3$N-)46`|36m*sc#4%C|h zBN<2U;7k>&G_wL4=Ve5z`ubVD&*Hxi)r@{4RCDw7U_D`lbC(9&pG5C*z#W>8>HU)h z!h3g?2UL&sS!oY5$3?VlA0Me9W5e~V;2jds*fz^updz#AJ%G8w2V}AEE?E^=MK%Xt z__Bx1cr7+DQmuHmzn*|hh%~eEc9@m05@clWfpEFcr+06%0&dZJH&@8^&@*$qR@}o3 z@Tuuh2FsLz^zH+dN&T&?0G3I?MpmYJ;GP$J!EzjeM#YLJ!W$}MVNb0^HfOA>5Fe~UNn%Zk(PT@~9}1dt)1UQ zU*B5K?Dl#G74qmg|2>^>0WtLX#Jz{lO4NT`NYB*(L#D|5IpXr9v&7a@YsGp3vLR7L zHYGHZg7{ie6n~2p$6Yz>=^cEg7tEgk-1YRl%-s7^cbqFb(U7&Dp78+&ut5!Tn(hER z|Gp4Ed@CnOPeAe|N>U(dB;SZ?NU^AzoD^UAH_vamp6Ws}{|mSq`^+VP1g~2B{%N-!mWz<`)G)>V-<`9`L4?3dM%Qh6<@kba+m`JS{Ya@9Fq*m6$$ zA1%Ogc~VRH33|S9l%CNb4zM%k^EIpqY}@h{w(aBcJ9c05oiZx#SK9t->5lSI`=&l~ z+-Ic)a{FbBhXV$Xt!WRd`R#Jk-$+_Z52rS>?Vpt2IK<84|E-SBEoIw>cs=a{BlQ7O z-?{Fy_M&84&9|KM5wt~)*!~i~E=(6m8(uCO)I=)M?)&sRbzH$9Rovzd?ZEY}GqX+~ zFbEbLz`BZ49=2Yh-|<`waK-_4!7`ro@zlC|r&I4fc4oyb+m=|c8)8%tZ-z5FwhzDt zL5kB@u53`d@%nHl0Sp)Dw`(QU&>vujEn?GPEXUW!Wi<+4e%BORl&BIH+SwRcbS}X@ z01Pk|vA%OdJKAs17zSXtO55k!;%m9>1eW9LnyAX4uj7@${O6cfii`49qTNItzny5J zH&Gj`e}o}?xjQ}r?LrI%FjUd@xflT3|7LA|ka%Q3i}a8gVm<`HIWoJGH=$EGClX^C0lysQJ>UO(q&;`T#8txuoQ_{l^kEV9CAdXuU1Ghg8 zN_6hHFuy&1x24q5-(Z7;!poYdt*`UTdrQOIQ!2O7_+AHV2hgXaEz7)>$LEdG z<8vE^Tw$|YwZHZDPM!SNOAWG$?J)MdmEk{U!!$M#fp7*Wo}jJ$Q(=8>R`Ats?e|VU?Zt7Cdh%AdnfyN3MBWw{ z$OnREvPf7%z6`#2##_7id|H%Y{vV^vWXb?5d5?a_y&t3@p9t$ncHj-NBdo&X{wrfJ zamN)VMYROYh_SvjJ=Xd!Ga?PY_$;*L=SxFte!4O6%0HEh%iZ4=gvns7IWIyJHa|hT z2;1+e)`TvbNb3-0z&DD_)Jomsg-7p_Uh`wjGnU1urmv1_oVqRg#=C?e?!7DgtqojU zWoAB($&53;TsXu^@2;8M`#z{=rPy?JqgYM0CDf4v@z=ZD|ItJ&8%_7A#K?S{wjxgd z?xA6JdJojrWpB7fr2p_MSsU4(R7=XGS0+Eg#xR=j>`H@R9{XjwBmqAiOxOL` zt?XK-iTEOWV}f>Pz3H-s*>W z4~8C&Xq25UQ^xH6H9kY_RM1$ch+%YLF72AA7^b{~VNTG}Tj#qZltz5Q=qxR`&oIlW Nr__JTFzvMr^FKp4S3v*( literal 0 HcmV?d00001 diff --git a/src/Vegasco-Web/src/app/app.config.ts b/src/Vegasco-Web/src/app/app.config.ts new file mode 100644 index 0000000..d953f4c --- /dev/null +++ b/src/Vegasco-Web/src/app/app.config.ts @@ -0,0 +1,12 @@ +import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core'; +import { provideRouter } from '@angular/router'; + +import { routes } from './app.routes'; + +export const appConfig: ApplicationConfig = { + providers: [ + provideBrowserGlobalErrorListeners(), + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes) + ] +}; diff --git a/src/Vegasco-Web/src/app/app.html b/src/Vegasco-Web/src/app/app.html new file mode 100644 index 0000000..36093e1 --- /dev/null +++ b/src/Vegasco-Web/src/app/app.html @@ -0,0 +1,336 @@ + + + + + + + + + + + +
+
+
+ +

Hello, {{ title }}

+

Congratulations! Your app is running. 🎉

+
+ +
+
+ @for (item of [ + { title: 'Explore the Docs', link: 'https://angular.dev' }, + { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, + { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, + { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, + { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, + ]; track item.title) { + + {{ item.title }} + + + + + } +
+ +
+
+
+ + + + + + + + + + + diff --git a/src/Vegasco-Web/src/app/app.routes.ts b/src/Vegasco-Web/src/app/app.routes.ts new file mode 100644 index 0000000..dc39edb --- /dev/null +++ b/src/Vegasco-Web/src/app/app.routes.ts @@ -0,0 +1,3 @@ +import { Routes } from '@angular/router'; + +export const routes: Routes = []; diff --git a/src/Vegasco-Web/src/app/app.scss b/src/Vegasco-Web/src/app/app.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/Vegasco-Web/src/app/app.spec.ts b/src/Vegasco-Web/src/app/app.spec.ts new file mode 100644 index 0000000..33b1aed --- /dev/null +++ b/src/Vegasco-Web/src/app/app.spec.ts @@ -0,0 +1,23 @@ +import { TestBed } from '@angular/core/testing'; +import { App } from './app'; + +describe('App', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [App], + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(App); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('h1')?.textContent).toContain('Hello, Vegasco-Web'); + }); +}); diff --git a/src/Vegasco-Web/src/app/app.ts b/src/Vegasco-Web/src/app/app.ts new file mode 100644 index 0000000..98798e3 --- /dev/null +++ b/src/Vegasco-Web/src/app/app.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; + +@Component({ + selector: 'app-root', + imports: [RouterOutlet], + templateUrl: './app.html', + styleUrl: './app.scss' +}) +export class App { + protected title = 'Vegasco-Web'; +} diff --git a/src/Vegasco-Web/src/index.html b/src/Vegasco-Web/src/index.html new file mode 100644 index 0000000..334482a --- /dev/null +++ b/src/Vegasco-Web/src/index.html @@ -0,0 +1,13 @@ + + + + + VegascoWeb + + + + + + + + diff --git a/src/Vegasco-Web/src/main.ts b/src/Vegasco-Web/src/main.ts new file mode 100644 index 0000000..5df75f9 --- /dev/null +++ b/src/Vegasco-Web/src/main.ts @@ -0,0 +1,6 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { App } from './app/app'; + +bootstrapApplication(App, appConfig) + .catch((err) => console.error(err)); diff --git a/src/Vegasco-Web/src/styles.scss b/src/Vegasco-Web/src/styles.scss new file mode 100644 index 0000000..90d4ee0 --- /dev/null +++ b/src/Vegasco-Web/src/styles.scss @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/src/Vegasco-Web/tsconfig.app.json b/src/Vegasco-Web/tsconfig.app.json new file mode 100644 index 0000000..264f459 --- /dev/null +++ b/src/Vegasco-Web/tsconfig.app.json @@ -0,0 +1,15 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "src/**/*.spec.ts" + ] +} diff --git a/src/Vegasco-Web/tsconfig.json b/src/Vegasco-Web/tsconfig.json new file mode 100644 index 0000000..e4955f2 --- /dev/null +++ b/src/Vegasco-Web/tsconfig.json @@ -0,0 +1,34 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "compileOnSave": false, + "compilerOptions": { + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "isolatedModules": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "ES2022", + "module": "preserve" + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false, + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "typeCheckHostBindings": true, + "strictTemplates": true + }, + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/src/Vegasco-Web/tsconfig.spec.json b/src/Vegasco-Web/tsconfig.spec.json new file mode 100644 index 0000000..04df34c --- /dev/null +++ b/src/Vegasco-Web/tsconfig.spec.json @@ -0,0 +1,14 @@ +/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ +/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "include": [ + "src/**/*.ts" + ] +} -- 2.49.1 From 20ba638b6434a341a5071a6335d535dbef631eec Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 13 Jun 2025 20:12:26 +0200 Subject: [PATCH 067/150] Fix server info endpoint not being accessibly without authentication --- .../Endpoints/EndpointExtensions.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/Vegasco.Server.Api/Endpoints/EndpointExtensions.cs b/src/Vegasco.Server.Api/Endpoints/EndpointExtensions.cs index c389ef7..1febd76 100644 --- a/src/Vegasco.Server.Api/Endpoints/EndpointExtensions.cs +++ b/src/Vegasco.Server.Api/Endpoints/EndpointExtensions.cs @@ -12,24 +12,33 @@ public static class EndpointExtensions public static void MapEndpoints(this IEndpointRouteBuilder builder) { ApiVersionSet apiVersionSet = builder.NewApiVersionSet() - .HasApiVersion(1.0) - .Build(); + .HasApiVersion(1.0) + .Build(); RouteGroupBuilder versionedApis = builder.MapGroup("/v{apiVersion:apiVersion}") - .WithApiVersionSet(apiVersionSet) + .WithApiVersionSet(apiVersionSet); + + GetCar.MapEndpoint(versionedApis) + .RequireAuthorization(Constants.Authorization.RequireAuthenticatedUserPolicy); + GetCars.MapEndpoint(versionedApis) + .RequireAuthorization(Constants.Authorization.RequireAuthenticatedUserPolicy); + CreateCar.MapEndpoint(versionedApis) + .RequireAuthorization(Constants.Authorization.RequireAuthenticatedUserPolicy); + UpdateCar.MapEndpoint(versionedApis) + .RequireAuthorization(Constants.Authorization.RequireAuthenticatedUserPolicy); + DeleteCar.MapEndpoint(versionedApis) .RequireAuthorization(Constants.Authorization.RequireAuthenticatedUserPolicy); - GetCar.MapEndpoint(versionedApis); - GetCars.MapEndpoint(versionedApis); - CreateCar.MapEndpoint(versionedApis); - UpdateCar.MapEndpoint(versionedApis); - DeleteCar.MapEndpoint(versionedApis); - - GetConsumptions.MapEndpoint(versionedApis); - GetConsumption.MapEndpoint(versionedApis); - CreateConsumption.MapEndpoint(versionedApis); - UpdateConsumption.MapEndpoint(versionedApis); - DeleteConsumption.MapEndpoint(versionedApis); + GetConsumptions.MapEndpoint(versionedApis) + .RequireAuthorization(Constants.Authorization.RequireAuthenticatedUserPolicy); + GetConsumption.MapEndpoint(versionedApis) + .RequireAuthorization(Constants.Authorization.RequireAuthenticatedUserPolicy); + CreateConsumption.MapEndpoint(versionedApis) + .RequireAuthorization(Constants.Authorization.RequireAuthenticatedUserPolicy); + UpdateConsumption.MapEndpoint(versionedApis) + .RequireAuthorization(Constants.Authorization.RequireAuthenticatedUserPolicy); + DeleteConsumption.MapEndpoint(versionedApis) + .RequireAuthorization(Constants.Authorization.RequireAuthenticatedUserPolicy); GetServerInfo.MapEndpoint(versionedApis); } -- 2.49.1 From f00e3cdb6ab41c9069b02e8079a0850099ce1cdc Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 13 Jun 2025 20:12:43 +0200 Subject: [PATCH 068/150] Add npm app to Aspire resources --- src/Vegasco.Server.AppHost/Program.cs | 8 +++++++- src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Vegasco.Server.AppHost/Program.cs b/src/Vegasco.Server.AppHost/Program.cs index 5d592bc..b56fd3e 100644 --- a/src/Vegasco.Server.AppHost/Program.cs +++ b/src/Vegasco.Server.AppHost/Program.cs @@ -7,9 +7,15 @@ IResourceBuilder postgres = builder.AddPostgres(Consta .WithDataVolume() .AddDatabase(Constants.Database.Name); -builder +IResourceBuilder api = builder .AddProject(Constants.Projects.Api) .WithReference(postgres) .WaitFor(postgres); +builder + .AddNpmApp("Vegasco-Web", "../Vegasco-Web") + .WithReference(api) + .WaitFor(api) + .WithHttpEndpoint(targetPort: 4200); + builder.Build().Run(); diff --git a/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj b/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj index e4de3b0..dc8b747 100644 --- a/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj +++ b/src/Vegasco.Server.AppHost/Vegasco.Server.AppHost.csproj @@ -13,6 +13,7 @@ + 3.7.115 -- 2.49.1 From 5727707cce9de035d7a82b3995d72bac81571851 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 13 Jun 2025 20:13:06 +0200 Subject: [PATCH 069/150] Add simply example for retrieving data from the api --- src/Vegasco-Web/angular.json | 3 ++ src/Vegasco-Web/proxy.config.js | 11 +++++ src/Vegasco-Web/src/app/app.config.ts | 4 +- src/Vegasco-Web/src/app/app.html | 66 +++++++++++++++++++-------- src/Vegasco-Web/src/app/app.ts | 27 +++++++++-- 5 files changed, 89 insertions(+), 22 deletions(-) create mode 100644 src/Vegasco-Web/proxy.config.js diff --git a/src/Vegasco-Web/angular.json b/src/Vegasco-Web/angular.json index 69eea0e..54594d0 100644 --- a/src/Vegasco-Web/angular.json +++ b/src/Vegasco-Web/angular.json @@ -59,6 +59,9 @@ }, "serve": { "builder": "@angular/build:dev-server", + "options": { + "proxyConfig": "proxy.config.js" + }, "configurations": { "production": { "buildTarget": "Vegasco-Web:build:production" diff --git a/src/Vegasco-Web/proxy.config.js b/src/Vegasco-Web/proxy.config.js new file mode 100644 index 0000000..dc3fb38 --- /dev/null +++ b/src/Vegasco-Web/proxy.config.js @@ -0,0 +1,11 @@ +module.exports = { + "/api": { + target: + process.env["services__Vegasco-Server-Api__https__0"] || + process.env["services__Vegasco-Server-Api__http__0"], + secure: process.env["NODE_ENV"] !== "development", + pathRewrite: { + "^/api": "", + }, + }, +}; diff --git a/src/Vegasco-Web/src/app/app.config.ts b/src/Vegasco-Web/src/app/app.config.ts index d953f4c..72e9f2c 100644 --- a/src/Vegasco-Web/src/app/app.config.ts +++ b/src/Vegasco-Web/src/app/app.config.ts @@ -2,11 +2,13 @@ import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChang import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; +import {provideHttpClient} from '@angular/common/http'; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), provideZoneChangeDetection({ eventCoalescing: true }), - provideRouter(routes) + provideRouter(routes), + provideHttpClient(), ] }; diff --git a/src/Vegasco-Web/src/app/app.html b/src/Vegasco-Web/src/app/app.html index 36093e1..7cb32c6 100644 --- a/src/Vegasco-Web/src/app/app.html +++ b/src/Vegasco-Web/src/app/app.html @@ -37,8 +37,8 @@ --pill-accent: var(--bright-blue); font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, - Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", - "Segoe UI Symbol"; + Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol"; box-sizing: border-box; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -52,8 +52,8 @@ letter-spacing: -0.125rem; margin: 0; font-family: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, - Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", - "Segoe UI Symbol"; + Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol"; } p { @@ -133,9 +133,11 @@ .pill-group .pill:nth-child(6n + 1) { --pill-accent: var(--bright-blue); } + .pill-group .pill:nth-child(6n + 2) { --pill-accent: var(--french-violet); } + .pill-group .pill:nth-child(6n + 3), .pill-group .pill:nth-child(6n + 4), .pill-group .pill:nth-child(6n + 5) { @@ -158,6 +160,10 @@ fill: var(--gray-400); } + td, th { + padding: .5rem; + } + .social-links a:hover svg path { fill: var(--gray-900); } @@ -205,9 +211,9 @@ gradientTransform="rotate(118.122 171.182 60.81) scale(205.794)" gradientUnits="userSpaceOnUse" > - - - + + + - - - - + + + + - + + +

Hello, {{ title }}

Congratulations! Your app is running. 🎉

+
+ @if (serverInfo$ | async; as serverInfo) { + + + + + + + + + + + + + + + + + +
VersionCommit IDCommit DateEnvironment
{{ serverInfo.fullVersion }}{{ serverInfo.commitId }}{{ serverInfo.commitDate | date:"dd.MM.yyyy HH:mm:ss" }}{{ serverInfo.environment }}
+ } +
@for (item of [ - { title: 'Explore the Docs', link: 'https://angular.dev' }, - { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' }, - { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' }, - { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' }, - { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' }, + {title: 'Explore the Docs', link: 'https://angular.dev'}, + {title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials'}, + {title: 'CLI Docs', link: 'https://angular.dev/tools/cli'}, + {title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service'}, + {title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools'}, ]; track item.title) { - + diff --git a/src/Vegasco-Web/src/app/app.ts b/src/Vegasco-Web/src/app/app.ts index 98798e3..b9a2c0a 100644 --- a/src/Vegasco-Web/src/app/app.ts +++ b/src/Vegasco-Web/src/app/app.ts @@ -1,12 +1,33 @@ -import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import {Component, inject} from '@angular/core'; +import {RouterOutlet} from '@angular/router'; +import {HttpClient} from '@angular/common/http'; +import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; +import {AsyncPipe, DatePipe} from '@angular/common'; +import {tap} from 'rxjs'; @Component({ selector: 'app-root', - imports: [RouterOutlet], + imports: [AsyncPipe, DatePipe, RouterOutlet], templateUrl: './app.html', styleUrl: './app.scss' }) export class App { protected title = 'Vegasco-Web'; + + private readonly http = inject(HttpClient); + + protected readonly serverInfo$; + + constructor() { + this.serverInfo$ = this.http.get('/api/v1/info/server') + .pipe(takeUntilDestroyed()); + } } + +interface ServerInfo { + fullVersion: string; + commitId: string; + commitDate: string; + environment: string; +} + -- 2.49.1 From f426368d15f86987259889939a826fe1149c4bc2 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 14 Jun 2025 20:30:00 +0200 Subject: [PATCH 070/150] Use random port for web app --- src/Vegasco-Web/package.json | 5 ++++- src/Vegasco-Web/pnpm-lock.yaml | 9 +++++++++ src/Vegasco.Server.AppHost/Program.cs | 3 ++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Vegasco-Web/package.json b/src/Vegasco-Web/package.json index a2bfe60..1dcc17f 100644 --- a/src/Vegasco-Web/package.json +++ b/src/Vegasco-Web/package.json @@ -3,7 +3,9 @@ "version": "0.0.0", "scripts": { "ng": "ng", - "start": "ng serve", + "start": "run-script-os", + "start:win32": "ng serve --port %PORT%", + "start:default": "ng serve --port $PORT", "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test" @@ -31,6 +33,7 @@ "karma-coverage": "~2.2.1", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", + "run-script-os": "^1.1.6", "typescript": "~5.8.3" } } diff --git a/src/Vegasco-Web/pnpm-lock.yaml b/src/Vegasco-Web/pnpm-lock.yaml index dedd530..c281cbf 100644 --- a/src/Vegasco-Web/pnpm-lock.yaml +++ b/src/Vegasco-Web/pnpm-lock.yaml @@ -66,6 +66,9 @@ importers: karma-jasmine-html-reporter: specifier: ~2.1.0 version: 2.1.0(jasmine-core@5.7.1)(karma-jasmine@5.1.0(karma@6.4.4))(karma@6.4.4) + run-script-os: + specifier: ^1.1.6 + version: 1.1.6 typescript: specifier: ~5.8.3 version: 5.8.3 @@ -2166,6 +2169,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + run-script-os@1.1.6: + resolution: {integrity: sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw==} + hasBin: true + rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} @@ -4700,6 +4707,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.40.2 fsevents: 2.3.3 + run-script-os@1.1.6: {} + rxjs@7.8.2: dependencies: tslib: 2.8.1 diff --git a/src/Vegasco.Server.AppHost/Program.cs b/src/Vegasco.Server.AppHost/Program.cs index b56fd3e..2cdb72d 100644 --- a/src/Vegasco.Server.AppHost/Program.cs +++ b/src/Vegasco.Server.AppHost/Program.cs @@ -16,6 +16,7 @@ builder .AddNpmApp("Vegasco-Web", "../Vegasco-Web") .WithReference(api) .WaitFor(api) - .WithHttpEndpoint(targetPort: 4200); + .WithHttpEndpoint(port: 44200, env: "PORT") + .WithExternalHttpEndpoints(); builder.Build().Run(); -- 2.49.1 From 7d6f85db8224c1975700b161f975edc17b04b181 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 14 Jun 2025 20:30:47 +0200 Subject: [PATCH 071/150] Try fetching data from protected endpoint --- nuget.config | 7 + src/Vegasco-Web/angular.json | 16 +- src/Vegasco-Web/package.json | 1 + src/Vegasco-Web/pnpm-lock.yaml | 24 ++ .../src/app/api/models/consumption-entry.ts | 8 + src/Vegasco-Web/src/app/app.config.ts | 11 +- src/Vegasco-Web/src/app/app.html | 363 +----------------- src/Vegasco-Web/src/app/app.routes.ts | 12 +- src/Vegasco-Web/src/app/app.ts | 27 +- src/Vegasco-Web/src/app/auth/auth.config.ts | 45 +++ .../src/app/modules/entries/entries.routes.ts | 9 + .../entries/entries/entries.component.html | 22 ++ .../entries/entries/entries.component.scss | 3 + .../entries/entries/entries.component.ts | 27 ++ .../environments/environment.development.ts | 11 + .../src/environments/environment.interface.ts | 17 + .../environments/environment.production.ts | 11 + .../src/environments/environment.ts | 11 + 18 files changed, 232 insertions(+), 393 deletions(-) create mode 100644 nuget.config create mode 100644 src/Vegasco-Web/src/app/api/models/consumption-entry.ts create mode 100644 src/Vegasco-Web/src/app/auth/auth.config.ts create mode 100644 src/Vegasco-Web/src/app/modules/entries/entries.routes.ts create mode 100644 src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html create mode 100644 src/Vegasco-Web/src/app/modules/entries/entries/entries.component.scss create mode 100644 src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts create mode 100644 src/Vegasco-Web/src/environments/environment.development.ts create mode 100644 src/Vegasco-Web/src/environments/environment.interface.ts create mode 100644 src/Vegasco-Web/src/environments/environment.production.ts create mode 100644 src/Vegasco-Web/src/environments/environment.ts diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..432b482 --- /dev/null +++ b/nuget.config @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Vegasco-Web/angular.json b/src/Vegasco-Web/angular.json index 54594d0..e053d58 100644 --- a/src/Vegasco-Web/angular.json +++ b/src/Vegasco-Web/angular.json @@ -47,12 +47,24 @@ "maximumError": "8kB" } ], - "outputHashing": "all" + "outputHashing": "all", + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.production.ts" + } + ] }, "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true + "sourceMap": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.development.ts" + } + ] } }, "defaultConfiguration": "production" diff --git a/src/Vegasco-Web/package.json b/src/Vegasco-Web/package.json index 1dcc17f..c83237b 100644 --- a/src/Vegasco-Web/package.json +++ b/src/Vegasco-Web/package.json @@ -18,6 +18,7 @@ "@angular/forms": "^20.0.3", "@angular/platform-browser": "^20.0.3", "@angular/router": "^20.0.3", + "keycloak-angular": "^19.0.2", "rxjs": "~7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.1" diff --git a/src/Vegasco-Web/pnpm-lock.yaml b/src/Vegasco-Web/pnpm-lock.yaml index c281cbf..bea5b4e 100644 --- a/src/Vegasco-Web/pnpm-lock.yaml +++ b/src/Vegasco-Web/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: '@angular/router': specifier: ^20.0.3 version: 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + keycloak-angular: + specifier: ^19.0.2 + version: 19.0.2(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(keycloak-js@26.2.0) rxjs: specifier: ~7.8.2 version: 7.8.2 @@ -1769,6 +1772,17 @@ packages: engines: {node: '>= 10'} hasBin: true + keycloak-angular@19.0.2: + resolution: {integrity: sha512-GzQKC/jFJLZRmUxWOEXkla+6shDAZFAOe6Z3qsw916Ckb/UhZnO704HMZrd8xyVB3RH6xOcNCp45oHmIiqJ7dA==} + peerDependencies: + '@angular/common': ^19 + '@angular/core': ^19 + '@angular/router': ^19 + keycloak-js: ^18 || ^19 || ^20 || ^21 || ^22 || ^23 || ^24 || ^25 || ^26 + + keycloak-js@26.2.0: + resolution: {integrity: sha512-CrFcXTN+d6J0V/1v3Zpioys6qHNWE6yUzVVIsCUAmFn9H14GZ0vuYod+lt+SSpMgWGPuneDZBSGBAeLBFuqjsw==} + listr2@8.3.3: resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} engines: {node: '>=18.0.0'} @@ -4244,6 +4258,16 @@ snapshots: - supports-color - utf-8-validate + keycloak-angular@19.0.2(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(keycloak-js@26.2.0): + dependencies: + '@angular/common': 20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/router': 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + keycloak-js: 26.2.0 + tslib: 2.8.1 + + keycloak-js@26.2.0: {} + listr2@8.3.3: dependencies: cli-truncate: 4.0.0 diff --git a/src/Vegasco-Web/src/app/api/models/consumption-entry.ts b/src/Vegasco-Web/src/app/api/models/consumption-entry.ts new file mode 100644 index 0000000..66cd3fc --- /dev/null +++ b/src/Vegasco-Web/src/app/api/models/consumption-entry.ts @@ -0,0 +1,8 @@ +interface ConsumptionEntry { + id: string; + dateTime: string; + distance: number; + amount: number; + ignoreInCalculation: boolean; + carId: string; +} diff --git a/src/Vegasco-Web/src/app/app.config.ts b/src/Vegasco-Web/src/app/app.config.ts index 72e9f2c..3df29db 100644 --- a/src/Vegasco-Web/src/app/app.config.ts +++ b/src/Vegasco-Web/src/app/app.config.ts @@ -1,14 +1,17 @@ import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core'; -import { provideRouter } from '@angular/router'; +import { provideRouter, withComponentInputBinding } from '@angular/router'; import { routes } from './app.routes'; -import {provideHttpClient} from '@angular/common/http'; +import {provideHttpClient, withInterceptors} from '@angular/common/http'; +import { provideKeycloakAngular } from './auth/auth.config'; +import { includeBearerTokenInterceptor } from 'keycloak-angular'; export const appConfig: ApplicationConfig = { providers: [ + provideKeycloakAngular(), provideBrowserGlobalErrorListeners(), provideZoneChangeDetection({ eventCoalescing: true }), - provideRouter(routes), - provideHttpClient(), + provideRouter(routes, withComponentInputBinding()), + provideHttpClient(withInterceptors([includeBearerTokenInterceptor])), ] }; diff --git a/src/Vegasco-Web/src/app/app.html b/src/Vegasco-Web/src/app/app.html index 7cb32c6..530a5f8 100644 --- a/src/Vegasco-Web/src/app/app.html +++ b/src/Vegasco-Web/src/app/app.html @@ -1,366 +1,5 @@ - - - - - - - - - - -
- - - - - - - - - - - diff --git a/src/Vegasco-Web/src/app/app.routes.ts b/src/Vegasco-Web/src/app/app.routes.ts index dc39edb..d6a5093 100644 --- a/src/Vegasco-Web/src/app/app.routes.ts +++ b/src/Vegasco-Web/src/app/app.routes.ts @@ -1,3 +1,13 @@ import { Routes } from '@angular/router'; -export const routes: Routes = []; +export const routes: Routes = [ + { + path: '', + redirectTo: 'entries', + pathMatch: 'full' + }, + { + path: 'entries', + loadChildren: () => import('./modules/entries/entries.routes').then(m => m.routes) + } +]; diff --git a/src/Vegasco-Web/src/app/app.ts b/src/Vegasco-Web/src/app/app.ts index b9a2c0a..98798e3 100644 --- a/src/Vegasco-Web/src/app/app.ts +++ b/src/Vegasco-Web/src/app/app.ts @@ -1,33 +1,12 @@ -import {Component, inject} from '@angular/core'; -import {RouterOutlet} from '@angular/router'; -import {HttpClient} from '@angular/common/http'; -import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; -import {AsyncPipe, DatePipe} from '@angular/common'; -import {tap} from 'rxjs'; +import { Component } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; @Component({ selector: 'app-root', - imports: [AsyncPipe, DatePipe, RouterOutlet], + imports: [RouterOutlet], templateUrl: './app.html', styleUrl: './app.scss' }) export class App { protected title = 'Vegasco-Web'; - - private readonly http = inject(HttpClient); - - protected readonly serverInfo$; - - constructor() { - this.serverInfo$ = this.http.get('/api/v1/info/server') - .pipe(takeUntilDestroyed()); - } } - -interface ServerInfo { - fullVersion: string; - commitId: string; - commitDate: string; - environment: string; -} - diff --git a/src/Vegasco-Web/src/app/auth/auth.config.ts b/src/Vegasco-Web/src/app/auth/auth.config.ts new file mode 100644 index 0000000..ad6227b --- /dev/null +++ b/src/Vegasco-Web/src/app/auth/auth.config.ts @@ -0,0 +1,45 @@ +import { environment } from '../../environments/environment'; +import { + provideKeycloak, + createInterceptorCondition, + IncludeBearerTokenCondition, + INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, + withAutoRefreshToken, + AutoRefreshTokenService, + UserActivityService +} from 'keycloak-angular'; + +const serverHostBearerInterceptorCondition = createInterceptorCondition({ + // The API is consumed through a proxy running on the same origin as the application. + // This means that the interceptor should include the bearer token for requests to the same origin + // which includes requests starting to / which implicitly sends the request to the same origin. + urlPattern: new RegExp(`^(${window.origin}|/)`) +}); + +export const provideKeycloakAngular = () => + provideKeycloak({ + config: { + url: environment.keycloak.host, + realm: environment.keycloak.realm, + clientId: environment.keycloak.clientId, + }, + initOptions: { + onLoad: 'login-required', + silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html', + redirectUri: window.location.origin + '/', + checkLoginIframe: false, + }, + features: [ + withAutoRefreshToken({ + onInactivityTimeout: 'login', + }) + ], + providers: [ + AutoRefreshTokenService, + UserActivityService, + { + provide: INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, + useValue: [serverHostBearerInterceptorCondition] + } + ] + }); diff --git a/src/Vegasco-Web/src/app/modules/entries/entries.routes.ts b/src/Vegasco-Web/src/app/modules/entries/entries.routes.ts new file mode 100644 index 0000000..598ef33 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/entries.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from "@angular/router"; +import { EntriesComponent } from "./entries/entries.component"; + +export const routes: Routes = [ + { + path: '', + component: EntriesComponent + } +]; \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html new file mode 100644 index 0000000..dce0f49 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html @@ -0,0 +1,22 @@ +@if (consumptionEntries$ | async; as consumptionEntries) { +
+ + + + + + + + + + @for (entry of consumptionEntries; track entry.id) { + + + + + + } + +
DatumDistanzMenge
{{ entry.dateTime | date }}{{ entry.distance }} km{{ entry.amount }} l
+
+} \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.scss b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.scss new file mode 100644 index 0000000..e71c33b --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.scss @@ -0,0 +1,3 @@ +th, td { + padding: 0.5rem; +} \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts new file mode 100644 index 0000000..c9e5b8a --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -0,0 +1,27 @@ +import { AsyncPipe, DatePipe } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; +import { Component, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { Observable, tap } from 'rxjs'; + +@Component({ + selector: 'app-entries', + imports: [AsyncPipe, DatePipe], + templateUrl: './entries.component.html', + styleUrl: './entries.component.scss' +}) +export class EntriesComponent { + private readonly http = inject(HttpClient); + + protected readonly consumptionEntries$: Observable; + + constructor() { + this.consumptionEntries$ = this.http.get('/api/v1/consumptions') + .pipe( + takeUntilDestroyed(), + tap((response) => { + console.log('Entries response:', response); + }), + ); + } +} diff --git a/src/Vegasco-Web/src/environments/environment.development.ts b/src/Vegasco-Web/src/environments/environment.development.ts new file mode 100644 index 0000000..3135d63 --- /dev/null +++ b/src/Vegasco-Web/src/environments/environment.development.ts @@ -0,0 +1,11 @@ +import { Environment } from "./environment.interface"; + +export const environment: Environment = { + name: "Dev", + isProduction: false, + keycloak: { + host: "https://login.nuyken.dev", + realm: "development", + clientId: "vegasco" + } +}; diff --git a/src/Vegasco-Web/src/environments/environment.interface.ts b/src/Vegasco-Web/src/environments/environment.interface.ts new file mode 100644 index 0000000..ed18e6d --- /dev/null +++ b/src/Vegasco-Web/src/environments/environment.interface.ts @@ -0,0 +1,17 @@ + +/** The app's configuration based on the target environment */ +export interface Environment { + /** A name for this configuration, e.g. 'Prod' */ + name: string; + /** Whether this configuration is for production or not */ + isProduction: boolean; + /** Keycloak login configuration */ + keycloak: { + /** The host under which the keycloak is reachable */ + host: string; + /** The keycloak realm in which the client lives */ + realm: string; + /** The app's client id */ + clientId: string; + } +} diff --git a/src/Vegasco-Web/src/environments/environment.production.ts b/src/Vegasco-Web/src/environments/environment.production.ts new file mode 100644 index 0000000..5c0d939 --- /dev/null +++ b/src/Vegasco-Web/src/environments/environment.production.ts @@ -0,0 +1,11 @@ +import { Environment } from "./environment.interface"; + +export const environment: Environment = { + name: "Prod", + isProduction: true, + keycloak: { + host: "https://login.nuyken.dev", + realm: "apps", + clientId: "vegasco" + } +}; diff --git a/src/Vegasco-Web/src/environments/environment.ts b/src/Vegasco-Web/src/environments/environment.ts new file mode 100644 index 0000000..4f4cb10 --- /dev/null +++ b/src/Vegasco-Web/src/environments/environment.ts @@ -0,0 +1,11 @@ +import { Environment } from "./environment.interface"; + +export const environment: Environment = { + name: "", + isProduction: false, + keycloak: { + host: "", + realm: "", + clientId: "" + } +}; -- 2.49.1 From 4bf07b0972440d65d0504161be41d6941ece4bee Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 15 Jun 2025 09:36:52 +0200 Subject: [PATCH 072/150] Add editor config and apply code cleanup --- .editorconfig | 271 ++++++++++++++++++ src/Vegasco.Server.Api/Cars/GetCars.cs | 2 +- .../Common/DependencyInjectionExtensions.cs | 6 +- .../Common/StartupExtensions.cs | 4 +- .../Consumptions/GetConsumptions.cs | 76 ++--- .../Endpoints/EndpointExtensions.cs | 2 +- src/Vegasco.Server.Api/Info/GetServerInfo.cs | 40 +-- .../Migrations/20240818105918_Initial.cs | 150 +++++----- src/Vegasco.Server.AppHost/Program.cs | 4 +- .../Extensions.cs | 164 +++++------ .../Info/GetServerInfoTests.cs | 46 +-- 11 files changed, 516 insertions(+), 249 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5055012 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,271 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = tab +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = false:suggestion +dotnet_style_allow_statement_immediately_after_block_experimental = false:suggestion + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = true:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = file_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_top_level_statements = true:silent + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = true +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:suggestion +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = warning +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.private_or_internal_field_should_be_underscore.severity = warning +dotnet_naming_rule.private_or_internal_field_should_be_underscore.symbols = private_or_internal_field +dotnet_naming_rule.private_or_internal_field_should_be_underscore.style = underscore + +dotnet_naming_rule.private_or_internal_static_field_should_be_sunderscore.severity = warning +dotnet_naming_rule.private_or_internal_static_field_should_be_sunderscore.symbols = private_or_internal_static_field +dotnet_naming_rule.private_or_internal_static_field_should_be_sunderscore.style = sunderscore + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_field.required_modifiers = + +dotnet_naming_symbols.private_or_internal_static_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_static_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_static_field.required_modifiers = static + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.underscore.required_prefix = _ +dotnet_naming_style.underscore.required_suffix = +dotnet_naming_style.underscore.word_separator = +dotnet_naming_style.underscore.capitalization = camel_case + +dotnet_naming_style.sunderscore.required_prefix = s_ +dotnet_naming_style.sunderscore.required_suffix = +dotnet_naming_style.sunderscore.word_separator = +dotnet_naming_style.sunderscore.capitalization = camel_case + +[*.{cs,vb}] +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf \ No newline at end of file diff --git a/src/Vegasco.Server.Api/Cars/GetCars.cs b/src/Vegasco.Server.Api/Cars/GetCars.cs index cb4ce6e..0e009db 100644 --- a/src/Vegasco.Server.Api/Cars/GetCars.cs +++ b/src/Vegasco.Server.Api/Cars/GetCars.cs @@ -11,7 +11,7 @@ public static class GetCars { public IEnumerable Cars { get; set; } = []; } - + public record ResponseDto(Guid Id, string Name); public class Request diff --git a/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs b/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs index 7cccca9..44faa51 100644 --- a/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs +++ b/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs @@ -3,9 +3,7 @@ using FluentValidation; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Options; using System.Diagnostics; -using System.Globalization; using System.Reflection; -using Microsoft.AspNetCore.Localization; using Vegasco.Server.Api.Authentication; using Vegasco.Server.Api.Common; using Vegasco.Server.Api.Persistence; @@ -51,7 +49,7 @@ public static class DependencyInjectionExtensions services.AddHttpContextAccessor(); services.AddHostedService(); - + services.AddRequestLocalization(o => { string[] cultures = @@ -61,7 +59,7 @@ public static class DependencyInjectionExtensions "de-DE", "de" ]; - + o.SetDefaultCulture(cultures[0]) .AddSupportedCultures(cultures) .AddSupportedUICultures(cultures); diff --git a/src/Vegasco.Server.Api/Common/StartupExtensions.cs b/src/Vegasco.Server.Api/Common/StartupExtensions.cs index 4c1d06d..679a213 100644 --- a/src/Vegasco.Server.Api/Common/StartupExtensions.cs +++ b/src/Vegasco.Server.Api/Common/StartupExtensions.cs @@ -1,6 +1,4 @@ -using Microsoft.AspNetCore.Localization; -using System.Globalization; -using Vegasco.Server.Api.Endpoints; +using Vegasco.Server.Api.Endpoints; using Vegasco.Server.ServiceDefaults; namespace Vegasco.Server.Api.Common; diff --git a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs index 84fcebd..ad5fb76 100644 --- a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs @@ -7,47 +7,47 @@ namespace Vegasco.Server.Api.Consumptions; public static class GetConsumptions { - public class ApiResponse - { - public IEnumerable Consumptions { get; set; } = []; - } + public class ApiResponse + { + public IEnumerable Consumptions { get; set; } = []; + } - public record ResponseDto( - Guid Id, - DateTimeOffset DateTime, - double Distance, - double Amount, - bool IgnoreInCalculation, - Guid CarId); + public record ResponseDto( + Guid Id, + DateTimeOffset DateTime, + double Distance, + double Amount, + bool IgnoreInCalculation, + Guid CarId); - public class Request - { - [FromQuery(Name = "page")] public int? Page { get; set; } - [FromQuery(Name = "pageSize")] public int? PageSize { get; set; } - } + public class Request + { + [FromQuery(Name = "page")] public int? Page { get; set; } + [FromQuery(Name = "pageSize")] public int? PageSize { get; set; } + } - public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) - { - return builder - .MapGet("consumptions", Endpoint) - .WithDescription("Returns all consumption entries") - .WithTags("Consumptions"); - } + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapGet("consumptions", Endpoint) + .WithDescription("Returns all consumption entries") + .WithTags("Consumptions"); + } - private static async Task> Endpoint( - [AsParameters] Request request, - ApplicationDbContext dbContext, - CancellationToken cancellationToken) - { - List consumptions = await dbContext.Consumptions - .Select(x => - new ResponseDto(x.Id.Value, x.DateTime, x.Distance, x.Amount, x.IgnoreInCalculation, x.CarId.Value)) - .ToListAsync(cancellationToken); + private static async Task> Endpoint( + [AsParameters] Request request, + ApplicationDbContext dbContext, + CancellationToken cancellationToken) + { + List consumptions = await dbContext.Consumptions + .Select(x => + new ResponseDto(x.Id.Value, x.DateTime, x.Distance, x.Amount, x.IgnoreInCalculation, x.CarId.Value)) + .ToListAsync(cancellationToken); - var apiResponse = new ApiResponse - { - Consumptions = consumptions - }; - return TypedResults.Ok(apiResponse); - } + var apiResponse = new ApiResponse + { + Consumptions = consumptions + }; + return TypedResults.Ok(apiResponse); + } } \ No newline at end of file diff --git a/src/Vegasco.Server.Api/Endpoints/EndpointExtensions.cs b/src/Vegasco.Server.Api/Endpoints/EndpointExtensions.cs index 1febd76..0b5901d 100644 --- a/src/Vegasco.Server.Api/Endpoints/EndpointExtensions.cs +++ b/src/Vegasco.Server.Api/Endpoints/EndpointExtensions.cs @@ -17,7 +17,7 @@ public static class EndpointExtensions RouteGroupBuilder versionedApis = builder.MapGroup("/v{apiVersion:apiVersion}") .WithApiVersionSet(apiVersionSet); - + GetCar.MapEndpoint(versionedApis) .RequireAuthorization(Constants.Authorization.RequireAuthenticatedUserPolicy); GetCars.MapEndpoint(versionedApis) diff --git a/src/Vegasco.Server.Api/Info/GetServerInfo.cs b/src/Vegasco.Server.Api/Info/GetServerInfo.cs index 2e82096..e188769 100644 --- a/src/Vegasco.Server.Api/Info/GetServerInfo.cs +++ b/src/Vegasco.Server.Api/Info/GetServerInfo.cs @@ -4,26 +4,26 @@ namespace Vegasco.Server.Api.Info; public class GetServerInfo { - public record Response( - string FullVersion, - string CommitId, - DateTimeOffset CommitDate, - string Environment); + public record Response( + string FullVersion, + string CommitId, + DateTimeOffset CommitDate, + string Environment); - public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) - { - return builder - .MapGet("info/server", Endpoint) - .WithTags("Info"); - } + public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) + { + return builder + .MapGet("info/server", Endpoint) + .WithTags("Info"); + } - private static Ok Endpoint( - IHostEnvironment environment) - { - return TypedResults.Ok(new Response( - ThisAssembly.AssemblyInformationalVersion, - ThisAssembly.GitCommitId, - new DateTimeOffset(ThisAssembly.GitCommitDate, TimeSpan.Zero), - environment.EnvironmentName)); - } + private static Ok Endpoint( + IHostEnvironment environment) + { + return TypedResults.Ok(new Response( + ThisAssembly.AssemblyInformationalVersion, + ThisAssembly.GitCommitId, + new DateTimeOffset(ThisAssembly.GitCommitDate, TimeSpan.Zero), + environment.EnvironmentName)); + } } \ No newline at end of file diff --git a/src/Vegasco.Server.Api/Persistence/Migrations/20240818105918_Initial.cs b/src/Vegasco.Server.Api/Persistence/Migrations/20240818105918_Initial.cs index 90e5b37..039e09b 100644 --- a/src/Vegasco.Server.Api/Persistence/Migrations/20240818105918_Initial.cs +++ b/src/Vegasco.Server.Api/Persistence/Migrations/20240818105918_Initial.cs @@ -4,86 +4,86 @@ namespace Vegasco.Server.Api.Persistence.Migrations { - /// - public partial class Initial : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "Users", - columns: table => new - { - Id = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Users", x => x.Id); - }); + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + }); - migrationBuilder.CreateTable( - name: "Cars", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - Name = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), - UserId = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Cars", x => x.Id); - table.ForeignKey( - name: "FK_Cars_Users_UserId", - column: x => x.UserId, - principalTable: "Users", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "Cars", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(50)", maxLength: 50, nullable: false), + UserId = table.Column(type: "text", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Cars", x => x.Id); + table.ForeignKey( + name: "FK_Cars_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateTable( - name: "Consumptions", - columns: table => new - { - Id = table.Column(type: "uuid", nullable: false), - DateTime = table.Column(type: "timestamp with time zone", nullable: false), - Distance = table.Column(type: "double precision", nullable: false), - Amount = table.Column(type: "double precision", nullable: false), - IgnoreInCalculation = table.Column(type: "boolean", nullable: false), - CarId = table.Column(type: "uuid", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_Consumptions", x => x.Id); - table.ForeignKey( - name: "FK_Consumptions_Cars_CarId", - column: x => x.CarId, - principalTable: "Cars", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); + migrationBuilder.CreateTable( + name: "Consumptions", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + DateTime = table.Column(type: "timestamp with time zone", nullable: false), + Distance = table.Column(type: "double precision", nullable: false), + Amount = table.Column(type: "double precision", nullable: false), + IgnoreInCalculation = table.Column(type: "boolean", nullable: false), + CarId = table.Column(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Consumptions", x => x.Id); + table.ForeignKey( + name: "FK_Consumptions_Cars_CarId", + column: x => x.CarId, + principalTable: "Cars", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); - migrationBuilder.CreateIndex( - name: "IX_Cars_UserId", - table: "Cars", - column: "UserId"); + migrationBuilder.CreateIndex( + name: "IX_Cars_UserId", + table: "Cars", + column: "UserId"); - migrationBuilder.CreateIndex( - name: "IX_Consumptions_CarId", - table: "Consumptions", - column: "CarId"); - } + migrationBuilder.CreateIndex( + name: "IX_Consumptions_CarId", + table: "Consumptions", + column: "CarId"); + } - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "Consumptions"); + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Consumptions"); - migrationBuilder.DropTable( - name: "Cars"); + migrationBuilder.DropTable( + name: "Cars"); - migrationBuilder.DropTable( - name: "Users"); - } - } + migrationBuilder.DropTable( + name: "Users"); + } + } } diff --git a/src/Vegasco.Server.AppHost/Program.cs b/src/Vegasco.Server.AppHost/Program.cs index 2cdb72d..a4365d9 100644 --- a/src/Vegasco.Server.AppHost/Program.cs +++ b/src/Vegasco.Server.AppHost/Program.cs @@ -16,7 +16,7 @@ builder .AddNpmApp("Vegasco-Web", "../Vegasco-Web") .WithReference(api) .WaitFor(api) - .WithHttpEndpoint(port: 44200, env: "PORT") - .WithExternalHttpEndpoints(); + .WithHttpEndpoint(port: 44200, env: "PORT") + .WithExternalHttpEndpoints(); builder.Build().Run(); diff --git a/src/Vegasco.Server.ServiceDefaults/Extensions.cs b/src/Vegasco.Server.ServiceDefaults/Extensions.cs index 82fd99b..f602d29 100644 --- a/src/Vegasco.Server.ServiceDefaults/Extensions.cs +++ b/src/Vegasco.Server.ServiceDefaults/Extensions.cs @@ -15,105 +15,105 @@ namespace Vegasco.Server.ServiceDefaults; // To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults public static class Extensions { - public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder - { - builder.ConfigureOpenTelemetry(); + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); - builder.AddDefaultHealthChecks(); + builder.AddDefaultHealthChecks(); - builder.Services.AddServiceDiscovery(); + builder.Services.AddServiceDiscovery(); - builder.Services.ConfigureHttpClientDefaults(http => - { - // Turn on resilience by default - http.AddStandardResilienceHandler(); + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); - // Turn on service discovery by default - http.AddServiceDiscovery(); - }); + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); - // Uncomment the following to restrict the allowed schemes for service discovery. - // builder.Services.Configure(options => - // { - // options.AllowedSchemes = ["https"]; - // }); + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); - return builder; - } + return builder; + } - public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder - { - builder.Logging.AddOpenTelemetry(logging => - { - logging.IncludeFormattedMessage = true; - logging.IncludeScopes = true; - }); + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); - builder.Services.AddOpenTelemetry() - .WithMetrics(metrics => - { - metrics.AddAspNetCoreInstrumentation() - .AddHttpClientInstrumentation() - .AddRuntimeInstrumentation(); - }) - .WithTracing(tracing => - { - tracing.AddSource(builder.Environment.ApplicationName) - .AddAspNetCoreInstrumentation() - // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) - //.AddGrpcClientInstrumentation() - .AddHttpClientInstrumentation(); - }); + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation() + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); - builder.AddOpenTelemetryExporters(); + builder.AddOpenTelemetryExporters(); - return builder; - } + return builder; + } - private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder - { - bool useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + bool useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); - if (useOtlpExporter) - { - builder.Services.AddOpenTelemetry().UseOtlpExporter(); - } + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } - // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) - //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) - //{ - // builder.Services.AddOpenTelemetry() - // .UseAzureMonitor(); - //} + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} - return builder; - } + return builder; + } - public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder - { - builder.Services.AddHealthChecks() - // Add a default liveness check to ensure app is responsive - .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); - return builder; - } + return builder; + } - public static WebApplication MapDefaultEndpoints(this WebApplication app) - { - // Adding health checks endpoints to applications in non-development environments has security implications. - // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. - if (app.Environment.IsDevelopment()) - { - // All health checks must pass for app to be considered ready to accept traffic after starting - app.MapHealthChecks("/health"); + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); - // Only health checks tagged with the "live" tag must pass for app to be considered alive - app.MapHealthChecks("/alive", new HealthCheckOptions - { - Predicate = r => r.Tags.Contains("live") - }); - } + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } - return app; - } + return app; + } } diff --git a/tests/Vegasco.Server.Api.Tests.Integration/Info/GetServerInfoTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Info/GetServerInfoTests.cs index 2732bec..6c2f393 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/Info/GetServerInfoTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Info/GetServerInfoTests.cs @@ -1,6 +1,6 @@ -using System.Net.Http.Json; -using FluentAssertions; +using FluentAssertions; using FluentAssertions.Extensions; +using System.Net.Http.Json; using Vegasco.Server.Api.Info; namespace Vegasco.Server.Api.Tests.Integration.Info; @@ -8,28 +8,28 @@ namespace Vegasco.Server.Api.Tests.Integration.Info; [Collection(SharedTestCollection.Name)] public class GetServerInfoTests { - private readonly WebAppFactory _factory; + private readonly WebAppFactory _factory; - public GetServerInfoTests(WebAppFactory factory) - { - _factory = factory; - } + public GetServerInfoTests(WebAppFactory factory) + { + _factory = factory; + } - [Fact] - public async Task GetServerInfo_ShouldReturnServerInfo_WhenCalled() - { - // Arrange - - // Act - using HttpResponseMessage response = await _factory.HttpClient.GetAsync("/v1/info/server"); + [Fact] + public async Task GetServerInfo_ShouldReturnServerInfo_WhenCalled() + { + // Arrange - // Assert - response.IsSuccessStatusCode.Should().BeTrue(); - var serverInfo = await response.Content.ReadFromJsonAsync(); - serverInfo!.Environment.Should().NotBeEmpty(); - serverInfo.CommitDate.Should().BeAfter(23.August(2024)) - .And.NotBeAfter(DateTime.Now); - serverInfo.CommitId.Should().MatchRegex(@"[0-9a-f]{40}"); - serverInfo.FullVersion.Should().MatchRegex(@"\d\.\d\.\d(-[0-9a-zA-Z]+)?(\+g?[0-9a-f]{10})?"); - } + // Act + using HttpResponseMessage response = await _factory.HttpClient.GetAsync("/v1/info/server"); + + // Assert + response.IsSuccessStatusCode.Should().BeTrue(); + var serverInfo = await response.Content.ReadFromJsonAsync(); + serverInfo!.Environment.Should().NotBeEmpty(); + serverInfo.CommitDate.Should().BeAfter(23.August(2024)) + .And.NotBeAfter(DateTime.Now); + serverInfo.CommitId.Should().MatchRegex(@"[0-9a-f]{40}"); + serverInfo.FullVersion.Should().MatchRegex(@"\d\.\d\.\d(-[0-9a-zA-Z]+)?(\+g?[0-9a-f]{10})?"); + } } \ No newline at end of file -- 2.49.1 From a1174e3b4299df19e229272b716e0d82d08b228d Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 15 Jun 2025 11:29:46 +0200 Subject: [PATCH 073/150] Expose swagger UI again --- src/Vegasco.Server.Api/Common/StartupExtensions.cs | 14 +++++++++++++- src/Vegasco.Server.Api/Vegasco.Server.Api.csproj | 1 + 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Vegasco.Server.Api/Common/StartupExtensions.cs b/src/Vegasco.Server.Api/Common/StartupExtensions.cs index 679a213..ada640e 100644 --- a/src/Vegasco.Server.Api/Common/StartupExtensions.cs +++ b/src/Vegasco.Server.Api/Common/StartupExtensions.cs @@ -1,4 +1,5 @@ -using Vegasco.Server.Api.Endpoints; +using Asp.Versioning.ApiExplorer; +using Vegasco.Server.Api.Endpoints; using Vegasco.Server.ServiceDefaults; namespace Vegasco.Server.Api.Common; @@ -36,6 +37,17 @@ internal static class StartupExtensions if (app.Environment.IsDevelopment()) { app.MapOpenApi("/swagger/{documentName}/swagger.json"); + app.UseSwaggerUI(o => + { + // Create a Swagger endpoint for each API version + IReadOnlyList apiVersions = app.DescribeApiVersions(); + foreach (ApiVersionDescription apiVersionDescription in apiVersions) + { + string url = $"/swagger/{apiVersionDescription.GroupName}/swagger.json"; + string name = apiVersionDescription.GroupName.ToUpperInvariant(); + o.SwaggerEndpoint(url, name); + } + }); } return app; diff --git a/src/Vegasco.Server.Api/Vegasco.Server.Api.csproj b/src/Vegasco.Server.Api/Vegasco.Server.Api.csproj index f09473e..6b8436f 100644 --- a/src/Vegasco.Server.Api/Vegasco.Server.Api.csproj +++ b/src/Vegasco.Server.Api/Vegasco.Server.Api.csproj @@ -31,6 +31,7 @@ + -- 2.49.1 From edafe0e4ecfaf3df660cf5b00cb85f2a72623fee Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 15 Jun 2025 11:30:03 +0200 Subject: [PATCH 074/150] Fix API return type mismatch --- .../get-consumption-entries-response.ts | 3 ++ .../entries/entries/entries.component.html | 42 +++++++++---------- .../entries/entries/entries.component.ts | 5 ++- 3 files changed, 27 insertions(+), 23 deletions(-) create mode 100644 src/Vegasco-Web/src/app/api/models/get-consumption-entries-response.ts diff --git a/src/Vegasco-Web/src/app/api/models/get-consumption-entries-response.ts b/src/Vegasco-Web/src/app/api/models/get-consumption-entries-response.ts new file mode 100644 index 0000000..c4099c4 --- /dev/null +++ b/src/Vegasco-Web/src/app/api/models/get-consumption-entries-response.ts @@ -0,0 +1,3 @@ +interface GetConsumptionEntriesResponse { + consumptions: ConsumptionEntry[]; +} \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html index dce0f49..ab67d54 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html @@ -1,22 +1,22 @@ @if (consumptionEntries$ | async; as consumptionEntries) { -
- - - - - - - - - - @for (entry of consumptionEntries; track entry.id) { - - - - - - } - -
DatumDistanzMenge
{{ entry.dateTime | date }}{{ entry.distance }} km{{ entry.amount }} l
-
-} \ No newline at end of file +
+ + + + + + + + + + @for (entry of consumptionEntries; track entry.id) { + + + + + + } + +
DatumDistanzMenge
{{ entry.dateTime | date }}{{ entry.distance }} km{{ entry.amount }} l
+
+} diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index c9e5b8a..7015883 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -2,7 +2,7 @@ import { AsyncPipe, DatePipe } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { Component, inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { Observable, tap } from 'rxjs'; +import { map, Observable, tap } from 'rxjs'; @Component({ selector: 'app-entries', @@ -16,12 +16,13 @@ export class EntriesComponent { protected readonly consumptionEntries$: Observable; constructor() { - this.consumptionEntries$ = this.http.get('/api/v1/consumptions') + this.consumptionEntries$ = this.http.get('/api/v1/consumptions') .pipe( takeUntilDestroyed(), tap((response) => { console.log('Entries response:', response); }), + map(response => response.consumptions) ); } } -- 2.49.1 From 9fb0f584a631f82ca17325cee315dad56bd5afd5 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 15 Jun 2025 12:33:52 +0200 Subject: [PATCH 075/150] Update launch configurations --- src/Vegasco-Web/.vscode/tasks.json | 7 +++++++ src/Vegasco-Web/package.json | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Vegasco-Web/.vscode/tasks.json b/src/Vegasco-Web/.vscode/tasks.json index a298b5b..ef8c754 100644 --- a/src/Vegasco-Web/.vscode/tasks.json +++ b/src/Vegasco-Web/.vscode/tasks.json @@ -5,6 +5,13 @@ { "type": "npm", "script": "start", + "options": { + "env": { + "PORT": "4200", + "services__Vegasco-Server-Api__https__0": "https://localhost:7098", + "NODE_ENV": "development" + } + }, "isBackground": true, "problemMatcher": { "owner": "typescript", diff --git a/src/Vegasco-Web/package.json b/src/Vegasco-Web/package.json index c83237b..5da04c3 100644 --- a/src/Vegasco-Web/package.json +++ b/src/Vegasco-Web/package.json @@ -4,8 +4,8 @@ "scripts": { "ng": "ng", "start": "run-script-os", - "start:win32": "ng serve --port %PORT%", - "start:default": "ng serve --port $PORT", + "start:win32": "ng serve --port %PORT% --configuration development", + "start:default": "ng serve --port $PORT --configuration development", "build": "ng build", "watch": "ng build --watch --configuration development", "test": "ng test" -- 2.49.1 From 28148e4f6932093d2b7890b6ccbe3b5b073140ff Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 15 Jun 2025 12:49:31 +0200 Subject: [PATCH 076/150] Allow for the web app to be run separately to allow debugging --- src/Vegasco-Web/.vscode/launch.json | 2 +- src/Vegasco-Web/.vscode/tasks.json | 2 +- src/Vegasco.Server.AppHost/Program.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Vegasco-Web/.vscode/launch.json b/src/Vegasco-Web/.vscode/launch.json index 925af83..8907192 100644 --- a/src/Vegasco-Web/.vscode/launch.json +++ b/src/Vegasco-Web/.vscode/launch.json @@ -7,7 +7,7 @@ "type": "chrome", "request": "launch", "preLaunchTask": "npm: start", - "url": "http://localhost:4200/" + "url": "http://localhost:44200/", }, { "name": "ng test", diff --git a/src/Vegasco-Web/.vscode/tasks.json b/src/Vegasco-Web/.vscode/tasks.json index ef8c754..91e9a1b 100644 --- a/src/Vegasco-Web/.vscode/tasks.json +++ b/src/Vegasco-Web/.vscode/tasks.json @@ -7,7 +7,7 @@ "script": "start", "options": { "env": { - "PORT": "4200", + "PORT": "44200", "services__Vegasco-Server-Api__https__0": "https://localhost:7098", "NODE_ENV": "development" } diff --git a/src/Vegasco.Server.AppHost/Program.cs b/src/Vegasco.Server.AppHost/Program.cs index a4365d9..8441f3e 100644 --- a/src/Vegasco.Server.AppHost/Program.cs +++ b/src/Vegasco.Server.AppHost/Program.cs @@ -16,7 +16,7 @@ builder .AddNpmApp("Vegasco-Web", "../Vegasco-Web") .WithReference(api) .WaitFor(api) - .WithHttpEndpoint(port: 44200, env: "PORT") + .WithHttpEndpoint(port: 44200, env: "PORT", isProxied: false) .WithExternalHttpEndpoints(); builder.Build().Run(); -- 2.49.1 From b28bc04a5379c8ab2e02b75ccc27d9a3c261d26b Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 17:15:01 +0200 Subject: [PATCH 077/150] Add http health check for Angular app To have a better indication in the dashboard as to when the app has actually started, because the dashboard otherwise displays a running state after the launch command has been given, but then the app only begins to compile and takes a few seconds to actually launch --- src/Vegasco.Server.AppHost/Program.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Vegasco.Server.AppHost/Program.cs b/src/Vegasco.Server.AppHost/Program.cs index 8441f3e..6c3c0c5 100644 --- a/src/Vegasco.Server.AppHost/Program.cs +++ b/src/Vegasco.Server.AppHost/Program.cs @@ -17,6 +17,7 @@ builder .WithReference(api) .WaitFor(api) .WithHttpEndpoint(port: 44200, env: "PORT", isProxied: false) - .WithExternalHttpEndpoints(); + .WithExternalHttpEndpoints() + .WithHttpHealthCheck("/", 200); builder.Build().Run(); -- 2.49.1 From df93f8299f35f9aaffe5422bf3af01f9ec7791c8 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 17:16:00 +0200 Subject: [PATCH 078/150] Update Angular launch profile --- src/Vegasco-Web/.vscode/launch.json | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Vegasco-Web/.vscode/launch.json b/src/Vegasco-Web/.vscode/launch.json index 8907192..80a5fe9 100644 --- a/src/Vegasco-Web/.vscode/launch.json +++ b/src/Vegasco-Web/.vscode/launch.json @@ -3,18 +3,11 @@ "version": "0.2.0", "configurations": [ { - "name": "ng serve", + "name": "Launch (Chrome)", "type": "chrome", "request": "launch", "preLaunchTask": "npm: start", "url": "http://localhost:44200/", - }, - { - "name": "ng test", - "type": "chrome", - "request": "launch", - "preLaunchTask": "npm: test", - "url": "http://localhost:9876/debug.html" } ] } -- 2.49.1 From 766d060707d948e87b2c70f4ad7eb33d4f5497b0 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 17:25:06 +0200 Subject: [PATCH 079/150] Downgrade to Angular 19 Multiple packages are not officially compatible yet --- src/Vegasco-Web/package.json | 18 +- src/Vegasco-Web/pnpm-lock.yaml | 1393 ++++++++++++++++++-------------- 2 files changed, 782 insertions(+), 629 deletions(-) diff --git a/src/Vegasco-Web/package.json b/src/Vegasco-Web/package.json index 5da04c3..0fb3061 100644 --- a/src/Vegasco-Web/package.json +++ b/src/Vegasco-Web/package.json @@ -12,21 +12,21 @@ }, "private": true, "dependencies": { - "@angular/common": "^20.0.3", - "@angular/compiler": "^20.0.3", - "@angular/core": "^20.0.3", - "@angular/forms": "^20.0.3", - "@angular/platform-browser": "^20.0.3", - "@angular/router": "^20.0.3", + "@angular/common": "^19.2.14", + "@angular/compiler": "^19.2.14", + "@angular/core": "^19.2.14", + "@angular/forms": "^19.2.14", + "@angular/platform-browser": "^19.2.14", + "@angular/router": "^19.2.14", "keycloak-angular": "^19.0.2", "rxjs": "~7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.1" }, "devDependencies": { - "@angular/build": "^20.0.2", - "@angular/cli": "^20.0.2", - "@angular/compiler-cli": "^20.0.3", + "@angular/build": "^19.2.15", + "@angular/cli": "^19.2.15", + "@angular/compiler-cli": "^19.2.14", "@types/jasmine": "~5.1.8", "jasmine-core": "~5.7.1", "karma": "~6.4.4", diff --git a/src/Vegasco-Web/pnpm-lock.yaml b/src/Vegasco-Web/pnpm-lock.yaml index bea5b4e..2777dc5 100644 --- a/src/Vegasco-Web/pnpm-lock.yaml +++ b/src/Vegasco-Web/pnpm-lock.yaml @@ -9,26 +9,26 @@ importers: .: dependencies: '@angular/common': - specifier: ^20.0.3 - version: 20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + specifier: ^19.2.14 + version: 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/compiler': - specifier: ^20.0.3 - version: 20.0.3 + specifier: ^19.2.14 + version: 19.2.14 '@angular/core': - specifier: ^20.0.3 - version: 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) + specifier: ^19.2.14 + version: 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) '@angular/forms': - specifier: ^20.0.3 - version: 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + specifier: ^19.2.14 + version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@angular/platform-browser': - specifier: ^20.0.3 - version: 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)) + specifier: ^19.2.14 + version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)) '@angular/router': - specifier: ^20.0.3 - version: 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + specifier: ^19.2.14 + version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) keycloak-angular: specifier: ^19.0.2 - version: 19.0.2(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(keycloak-js@26.2.0) + version: 19.0.2(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(keycloak-js@26.2.0) rxjs: specifier: ~7.8.2 version: 7.8.2 @@ -40,14 +40,14 @@ importers: version: 0.15.1 devDependencies: '@angular/build': - specifier: ^20.0.2 - version: 20.0.2(@angular/compiler-cli@20.0.3(@angular/compiler@20.0.3)(typescript@5.8.3))(@angular/compiler@20.0.3)(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.0.1)(chokidar@4.0.3)(karma@6.4.4)(postcss@8.5.5)(tslib@2.8.1)(typescript@5.8.3) + specifier: ^19.2.15 + version: 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(karma@6.4.4)(postcss@8.5.6)(typescript@5.8.3) '@angular/cli': - specifier: ^20.0.2 - version: 20.0.2(@types/node@24.0.1)(chokidar@4.0.3) + specifier: ^19.2.15 + version: 19.2.15(@types/node@24.0.3)(chokidar@4.0.3) '@angular/compiler-cli': - specifier: ^20.0.3 - version: 20.0.3(@angular/compiler@20.0.3)(typescript@5.8.3) + specifier: ^19.2.14 + version: 19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3) '@types/jasmine': specifier: ~5.1.8 version: 5.1.8 @@ -82,50 +82,42 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@angular-devkit/architect@0.2000.2': - resolution: {integrity: sha512-adJYWJWuyXFtCOg2lZTV/7CImf4ifxd6c//VXuq5kx7AiSGTIH5Nf2xTQe8ZAZqytUmDgnoNMDhGRQ9b3C5TnA==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/architect@0.1902.15': + resolution: {integrity: sha512-RbqhStc6ZoRv57ZqLB36VOkBkAdU3nNezCvIs0AJV5V4+vLPMrb0hpIB0sF+9yMlMjWsolnRsj0/Fil+zQG3bw==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@angular-devkit/core@20.0.2': - resolution: {integrity: sha512-qqTSpcIw+TqJ6u/tkQzqgpwVelHsHr8Jhws1Vlx6E0L6E+cRILBK48i9ttE+oYkQlcopQ3VZAmzcZodXJ1SQ9Q==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/core@19.2.15': + resolution: {integrity: sha512-pU2RZYX6vhd7uLSdLwPnuBcr0mXJSjp3EgOXKsrlQFQZevc+Qs+2JdXgIElnOT/aDqtRtriDmLlSbtdE8n3ZbA==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: chokidar: ^4.0.0 peerDependenciesMeta: chokidar: optional: true - '@angular-devkit/schematics@20.0.2': - resolution: {integrity: sha512-r1aSZhcadLtUMhzUUfy+pkJdZW93z8WQtpGR24y88yFpPgDL5kY85VSlOzjGgo1vEs8Dd7ADcOcsVsUW8MxQ3A==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/schematics@19.2.15': + resolution: {integrity: sha512-kNOJ+3vekJJCQKWihNmxBkarJzNW09kP5a9E1SRNiQVNOUEeSwcRR0qYotM65nx821gNzjjhJXnAZ8OazWldrg==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@angular/build@20.0.2': - resolution: {integrity: sha512-nxha/dncAwEbY0nkgDWeiWSi+MSCJBuQbFf5bjTZ+pu0fS+5SOQllZKzZE9H+dms/JsLHm2YmPiScIYVvUenDw==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular/build@19.2.15': + resolution: {integrity: sha512-iE4fp4d5ALu702uoL6/YkjM2JlGEXZ5G+RVzq3W2jg/Ft6ISAQnRKB6mymtetDD6oD7i87e8uSu9kFVNBauX2w==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: - '@angular/compiler': ^20.0.0 - '@angular/compiler-cli': ^20.0.0 - '@angular/core': ^20.0.0 - '@angular/localize': ^20.0.0 - '@angular/platform-browser': ^20.0.0 - '@angular/platform-server': ^20.0.0 - '@angular/service-worker': ^20.0.0 - '@angular/ssr': ^20.0.2 + '@angular/compiler': ^19.0.0 || ^19.2.0-next.0 + '@angular/compiler-cli': ^19.0.0 || ^19.2.0-next.0 + '@angular/localize': ^19.0.0 || ^19.2.0-next.0 + '@angular/platform-server': ^19.0.0 || ^19.2.0-next.0 + '@angular/service-worker': ^19.0.0 || ^19.2.0-next.0 + '@angular/ssr': ^19.2.15 karma: ^6.4.0 less: ^4.2.0 - ng-packagr: ^20.0.0 + ng-packagr: ^19.0.0 || ^19.2.0-next.0 postcss: ^8.4.0 tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 - tslib: ^2.3.0 - typescript: '>=5.8 <5.9' - vitest: ^3.1.1 + typescript: '>=5.5 <5.9' peerDependenciesMeta: - '@angular/core': - optional: true '@angular/localize': optional: true - '@angular/platform-browser': - optional: true '@angular/platform-server': optional: true '@angular/service-worker': @@ -142,76 +134,65 @@ packages: optional: true tailwindcss: optional: true - vitest: - optional: true - '@angular/cli@20.0.2': - resolution: {integrity: sha512-LzBONPETA1uCZuylgZRYe+vImf8i+rRrwAgOBHWbW2wxut9ZQ8ZFwQgNkjvDhE7DLmsFV+GskfAs5+Td/5LZwA==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular/cli@19.2.15': + resolution: {integrity: sha512-YRIpARHWSOnWkHusUWTQgeUrPWMjWvtQrOkjWc6stF36z2KUzKMEng6EzUvH6sZolNSwVwOFpODEP0ut4aBkvQ==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} hasBin: true - '@angular/common@20.0.3': - resolution: {integrity: sha512-HqqVqaj+xzByWJOIrONVRkpvM6mRuGmC+m9wKixhc9f+xXsymVTBR6xg+G/RwyYP2NuC5chxIZbaJTz2Hj+6+g==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + '@angular/common@19.2.14': + resolution: {integrity: sha512-NcNklcuyqaTjOVGf7aru8APX9mjsnZ01gFZrn47BxHozhaR0EMRrotYQTdi8YdVjPkeYFYanVntSLfhyobq/jg==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: - '@angular/core': 20.0.3 + '@angular/core': 19.2.14 rxjs: ^6.5.3 || ^7.4.0 - '@angular/compiler-cli@20.0.3': - resolution: {integrity: sha512-u+fYnx1sRrwL0fd8kaAD2LqJjfe/Zj7zyOv0A3Ue7r8jzdNsPU8MWr/QyBaWlqSpPEpR+kD3xmDvRT9ra9RTBA==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + '@angular/compiler-cli@19.2.14': + resolution: {integrity: sha512-e9/h86ETjoIK2yTLE9aUeMCKujdg/du2pq7run/aINjop4RtnNOw+ZlSTUa6R65lP5CVwDup1kPytpAoifw8cA==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} hasBin: true peerDependencies: - '@angular/compiler': 20.0.3 - typescript: '>=5.8 <5.9' - peerDependenciesMeta: - typescript: - optional: true + '@angular/compiler': 19.2.14 + typescript: '>=5.5 <5.9' - '@angular/compiler@20.0.3': - resolution: {integrity: sha512-CShPNvqqV5Cleyho8CKtcFlt7l2thHPUdXZPtKHH3Zf43KojvJbJksZLBz6ZbyoQdgxNMYSfbh4h0UbSGtPOzQ==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + '@angular/compiler@19.2.14': + resolution: {integrity: sha512-ZqJDYOdhgKpVGNq3+n/Gbxma8DVYElDsoRe0tvNtjkWBVdaOxdZZUqmJ3kdCBsqD/aqTRvRBu0KGo9s2fCChkA==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} - '@angular/core@20.0.3': - resolution: {integrity: sha512-kB6w1bQgClfmkTbWJeD3vSLqX0e3uSaJD6KJ7XXT1IEaqUs4J+mKRKHQyxpJlpdUb7R+jDaHSM/vrVF15/L2rA==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + '@angular/core@19.2.14': + resolution: {integrity: sha512-EVErpW9tGqJ/wNcAN3G/ErH8pHCJ8mM1E6bsJ8UJIpDTZkpqqYjBMtZS9YWH5n3KwUd1tAkAB2w8FK125AjDUQ==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: - '@angular/compiler': 20.0.3 rxjs: ^6.5.3 || ^7.4.0 zone.js: ~0.15.0 - peerDependenciesMeta: - '@angular/compiler': - optional: true - zone.js: - optional: true - '@angular/forms@20.0.3': - resolution: {integrity: sha512-tb4M+c+/JnmPmtTb3+Si/DWGttnCEW5rvi4u55q+EFxYGQO0GeHa53yQTl1e2ngQLT9/kgmDAsJ2mt1Ql9N6xg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + '@angular/forms@19.2.14': + resolution: {integrity: sha512-hWtDOj2B0AuRTf+nkMJeodnFpDpmEK9OIhIv1YxcRe73ooaxrIdjgugkElO8I9Tj0E4/7m117ezhWDUkbqm1zA==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: - '@angular/common': 20.0.3 - '@angular/core': 20.0.3 - '@angular/platform-browser': 20.0.3 + '@angular/common': 19.2.14 + '@angular/core': 19.2.14 + '@angular/platform-browser': 19.2.14 rxjs: ^6.5.3 || ^7.4.0 - '@angular/platform-browser@20.0.3': - resolution: {integrity: sha512-cba0bibw9dJ8b+a2a8mwkiq5/HPiakY9P5OiJEVefN+2V/K9CND/pW+KIbW0/P6KhSSDQ29xgcGRseVtkjYLmg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + '@angular/platform-browser@19.2.14': + resolution: {integrity: sha512-hzkT5nmA64oVBQl6PRjdL4dIFT1n7lfM9rm5cAoS+6LUUKRgiE2d421Kpn/Hz3jaCJfo+calMIdtSMIfUJBmww==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: - '@angular/animations': 20.0.3 - '@angular/common': 20.0.3 - '@angular/core': 20.0.3 + '@angular/animations': 19.2.14 + '@angular/common': 19.2.14 + '@angular/core': 19.2.14 peerDependenciesMeta: '@angular/animations': optional: true - '@angular/router@20.0.3': - resolution: {integrity: sha512-FY2kMZjLh7NUKjSaZ1K26azl67T4aVnOD8PE/w1Ih3eQmSIlHniNP1NmCGMUy6t1O/ZV6sCSKkA5AZFv18wzIQ==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + '@angular/router@19.2.14': + resolution: {integrity: sha512-cBTWY9Jx7YhbmDYDb7Hqz4Q7UNIMlKTkdKToJd2pbhIXyoS+kHVQrySmyca+jgvYMjWnIjsAEa3dpje12D4mFw==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: - '@angular/common': 20.0.3 - '@angular/core': 20.0.3 - '@angular/platform-browser': 20.0.3 + '@angular/common': 19.2.14 + '@angular/core': 19.2.14 + '@angular/platform-browser': 19.2.14 rxjs: ^6.5.3 || ^7.4.0 '@babel/code-frame@7.27.1': @@ -222,8 +203,12 @@ packages: resolution: {integrity: sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==} engines: {node: '>=6.9.0'} - '@babel/core@7.27.1': - resolution: {integrity: sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==} + '@babel/core@7.26.10': + resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.9': + resolution: {integrity: sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==} engines: {node: '>=6.9.0'} '@babel/core@7.27.4': @@ -234,8 +219,8 @@ packages: resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.27.1': - resolution: {integrity: sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==} + '@babel/helper-annotate-as-pure@7.25.9': + resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -252,6 +237,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + '@babel/helper-split-export-declaration@7.24.7': resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} engines: {node: '>=6.9.0'} @@ -277,6 +266,12 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-syntax-import-attributes@7.26.0': + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -293,152 +288,152 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@esbuild/aix-ppc64@0.25.5': - resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} + '@esbuild/aix-ppc64@0.25.4': + resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.5': - resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} + '@esbuild/android-arm64@0.25.4': + resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.5': - resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} + '@esbuild/android-arm@0.25.4': + resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.5': - resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} + '@esbuild/android-x64@0.25.4': + resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.5': - resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} + '@esbuild/darwin-arm64@0.25.4': + resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.5': - resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} + '@esbuild/darwin-x64@0.25.4': + resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.5': - resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} + '@esbuild/freebsd-arm64@0.25.4': + resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.5': - resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} + '@esbuild/freebsd-x64@0.25.4': + resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.5': - resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} + '@esbuild/linux-arm64@0.25.4': + resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.5': - resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} + '@esbuild/linux-arm@0.25.4': + resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.5': - resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} + '@esbuild/linux-ia32@0.25.4': + resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.5': - resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} + '@esbuild/linux-loong64@0.25.4': + resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.5': - resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} + '@esbuild/linux-mips64el@0.25.4': + resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.5': - resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} + '@esbuild/linux-ppc64@0.25.4': + resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.5': - resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} + '@esbuild/linux-riscv64@0.25.4': + resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.5': - resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} + '@esbuild/linux-s390x@0.25.4': + resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.5': - resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} + '@esbuild/linux-x64@0.25.4': + resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.5': - resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} + '@esbuild/netbsd-arm64@0.25.4': + resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.5': - resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} + '@esbuild/netbsd-x64@0.25.4': + resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.5': - resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} + '@esbuild/openbsd-arm64@0.25.4': + resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.5': - resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} + '@esbuild/openbsd-x64@0.25.4': + resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.25.5': - resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} + '@esbuild/sunos-x64@0.25.4': + resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.5': - resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} + '@esbuild/win32-arm64@0.25.4': + resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.5': - resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} + '@esbuild/win32-ia32@0.25.4': + resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.5': - resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} + '@esbuild/win32-x64@0.25.4': + resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -452,8 +447,8 @@ packages: '@types/node': optional: true - '@inquirer/confirm@5.1.10': - resolution: {integrity: sha512-FxbQ9giWxUWKUk2O5XZ6PduVnH2CZ/fmMKMBkH71MHJvWr7WL5AHKevhzF1L5uYWB2P548o1RzVxrNd3dpmk6g==} + '@inquirer/confirm@5.1.12': + resolution: {integrity: sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -461,8 +456,8 @@ packages: '@types/node': optional: true - '@inquirer/confirm@5.1.12': - resolution: {integrity: sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==} + '@inquirer/confirm@5.1.6': + resolution: {integrity: sha512-6ZXYK3M1XmaVBZX6FCfChgtponnL0R6I7k8Nu+kaoNkT828FVZTcca1MqmWQipaW2oNREQl5AaPCUOOCVNdRMw==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -528,8 +523,8 @@ packages: '@types/node': optional: true - '@inquirer/prompts@7.5.1': - resolution: {integrity: sha512-5AOrZPf2/GxZ+SDRZ5WFplCA2TAQgK3OYrXCYmJL5NaTu4ECcoWFlfUZuw7Es++6Njv7iu/8vpYJhuzxUH76Vg==} + '@inquirer/prompts@7.3.2': + resolution: {integrity: sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -607,44 +602,39 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@listr2/prompt-adapter-inquirer@2.0.22': - resolution: {integrity: sha512-hV36ZoY+xKL6pYOt1nPNnkciFkn89KZwqLhAFzJvYysAvL5uBQdiADZx/8bIDXIukzzwG0QlPYolgMzQUtKgpQ==} + '@listr2/prompt-adapter-inquirer@2.0.18': + resolution: {integrity: sha512-0hz44rAcrphyXcA8IS7EJ2SCoaBZD2u5goE8S/e+q/DL+dOGpqpcLidVOFeLG3VgML62SXmfRLAhWt0zL1oW4Q==} engines: {node: '>=18.0.0'} peerDependencies: '@inquirer/prompts': '>= 3 < 8' - '@lmdb/lmdb-darwin-arm64@3.3.0': - resolution: {integrity: sha512-LipbQobyEfQtu8WixasaFUZZ+JCGlho4OWwWIQ5ol0rB1RKkcZvypu7sS1CBvofBGVAa3vbOh8IOGQMrbmL5dg==} + '@lmdb/lmdb-darwin-arm64@3.2.6': + resolution: {integrity: sha512-yF/ih9EJJZc72psFQbwnn8mExIWfTnzWJg+N02hnpXtDPETYLmQswIMBn7+V88lfCaFrMozJsUvcEQIkEPU0Gg==} cpu: [arm64] os: [darwin] - '@lmdb/lmdb-darwin-x64@3.3.0': - resolution: {integrity: sha512-yA+9P+ZeA3vg76BLXWeUomIAjxfmSmR2eg8fueHXDg5Xe1Xmkl9JCKuHXUhtJ+mMVcH12d5k4kJBLbyXTadfGQ==} + '@lmdb/lmdb-darwin-x64@3.2.6': + resolution: {integrity: sha512-5BbCumsFLbCi586Bb1lTWQFkekdQUw8/t8cy++Uq251cl3hbDIGEwD9HAwh8H6IS2F6QA9KdKmO136LmipRNkg==} cpu: [x64] os: [darwin] - '@lmdb/lmdb-linux-arm64@3.3.0': - resolution: {integrity: sha512-OeWvSgjXXZ/zmtLqqL78I3910F6UYpUubmsUU+iBHo6nTtjkpXms95rJtGrjkWQqwswKBD7xSMplbYC4LEsiPA==} + '@lmdb/lmdb-linux-arm64@3.2.6': + resolution: {integrity: sha512-l5VmJamJ3nyMmeD1ANBQCQqy7do1ESaJQfKPSm2IG9/ADZryptTyCj8N6QaYgIWewqNUrcbdMkJajRQAt5Qjfg==} cpu: [arm64] os: [linux] - '@lmdb/lmdb-linux-arm@3.3.0': - resolution: {integrity: sha512-EDYrW9kle+8wI19JCj/PhRnGoCN9bked5cdOPdo1wdgH/HzjgoLPFTn9DHlZccgTEVhp3O+bpWXdN/rWySVvjw==} + '@lmdb/lmdb-linux-arm@3.2.6': + resolution: {integrity: sha512-+6XgLpMb7HBoWxXj+bLbiiB4s0mRRcDPElnRS3LpWRzdYSe+gFk5MT/4RrVNqd2MESUDmb53NUXw1+BP69bjiQ==} cpu: [arm] os: [linux] - '@lmdb/lmdb-linux-x64@3.3.0': - resolution: {integrity: sha512-wDd02mt5ScX4+xd6g78zKBr6ojpgCJCTrllCAabjgap5FzuETqOqaQfKhO+tJuGWv/J5q+GIds6uY7rNFueOxg==} + '@lmdb/lmdb-linux-x64@3.2.6': + resolution: {integrity: sha512-nDYT8qN9si5+onHYYaI4DiauDMx24OAiuZAUsEqrDy+ja/3EbpXPX/VAkMV8AEaQhy3xc4dRC+KcYIvOFefJ4Q==} cpu: [x64] os: [linux] - '@lmdb/lmdb-win32-arm64@3.3.0': - resolution: {integrity: sha512-COotWhHJgzXULLiEjOgWQwqig6PoA+6ji6W+sDl6M1HhMXWIymEVHGs0edsVSNtsNSCAWMxJgR3asv6FNX/2EA==} - cpu: [arm64] - os: [win32] - - '@lmdb/lmdb-win32-x64@3.3.0': - resolution: {integrity: sha512-kqUgQH+l8HDbkAapx+aoko7Ez4X4DqkIraOqY/k0QY5EN/iialVlFpBUXh4wFXzirdmEVjbIUMrceUh0Kh8LeA==} + '@lmdb/lmdb-win32-x64@3.2.6': + resolution: {integrity: sha512-XlqVtILonQnG+9fH2N3Aytria7P/1fwDgDhl29rde96uH2sLB8CHORIf2PfuLVzFQJ7Uqp8py9AYwr3ZUCFfWg==} cpu: [x64] os: [win32] @@ -778,6 +768,18 @@ packages: resolution: {integrity: sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==} engines: {node: '>= 10'} + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + '@npmcli/agent@3.0.0': resolution: {integrity: sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==} engines: {node: ^18.17.0 || >=20.5.0} @@ -901,109 +903,104 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@rollup/rollup-android-arm-eabi@4.40.2': - resolution: {integrity: sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==} + '@rollup/rollup-android-arm-eabi@4.34.8': + resolution: {integrity: sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.40.2': - resolution: {integrity: sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==} + '@rollup/rollup-android-arm64@4.34.8': + resolution: {integrity: sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.40.2': - resolution: {integrity: sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==} + '@rollup/rollup-darwin-arm64@4.34.8': + resolution: {integrity: sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.40.2': - resolution: {integrity: sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==} + '@rollup/rollup-darwin-x64@4.34.8': + resolution: {integrity: sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.40.2': - resolution: {integrity: sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==} + '@rollup/rollup-freebsd-arm64@4.34.8': + resolution: {integrity: sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.40.2': - resolution: {integrity: sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==} + '@rollup/rollup-freebsd-x64@4.34.8': + resolution: {integrity: sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.40.2': - resolution: {integrity: sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==} + '@rollup/rollup-linux-arm-gnueabihf@4.34.8': + resolution: {integrity: sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.40.2': - resolution: {integrity: sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==} + '@rollup/rollup-linux-arm-musleabihf@4.34.8': + resolution: {integrity: sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.40.2': - resolution: {integrity: sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==} + '@rollup/rollup-linux-arm64-gnu@4.34.8': + resolution: {integrity: sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.40.2': - resolution: {integrity: sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==} + '@rollup/rollup-linux-arm64-musl@4.34.8': + resolution: {integrity: sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.40.2': - resolution: {integrity: sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==} + '@rollup/rollup-linux-loongarch64-gnu@4.34.8': + resolution: {integrity: sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': - resolution: {integrity: sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==} + '@rollup/rollup-linux-powerpc64le-gnu@4.34.8': + resolution: {integrity: sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.40.2': - resolution: {integrity: sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==} + '@rollup/rollup-linux-riscv64-gnu@4.34.8': + resolution: {integrity: sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.40.2': - resolution: {integrity: sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.40.2': - resolution: {integrity: sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==} + '@rollup/rollup-linux-s390x-gnu@4.34.8': + resolution: {integrity: sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.40.2': - resolution: {integrity: sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==} + '@rollup/rollup-linux-x64-gnu@4.34.8': + resolution: {integrity: sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.40.2': - resolution: {integrity: sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==} + '@rollup/rollup-linux-x64-musl@4.34.8': + resolution: {integrity: sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.40.2': - resolution: {integrity: sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==} + '@rollup/rollup-win32-arm64-msvc@4.34.8': + resolution: {integrity: sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.40.2': - resolution: {integrity: sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==} + '@rollup/rollup-win32-ia32-msvc@4.34.8': + resolution: {integrity: sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.40.2': - resolution: {integrity: sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==} + '@rollup/rollup-win32-x64-msvc@4.34.8': + resolution: {integrity: sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==} cpu: [x64] os: [win32] - '@schematics/angular@20.0.2': - resolution: {integrity: sha512-TyF+/hV+8flAa/Vu8xOQF241Syg9rdbZD1dARdm6edbLo8nwxmHdRsIulRektb7oD5CpTnxpvrcNJjX77nhv6A==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@schematics/angular@19.2.15': + resolution: {integrity: sha512-dz/eoFQKG09POSygpEDdlCehFIMo35HUM2rVV8lx9PfQEibpbGwl1NNQYEbqwVjTyCyD/ILyIXCWPE+EfTnG4g==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} '@sigstore/bundle@3.1.0': resolution: {integrity: sha512-Mm1E3/CmDDCz3nDhFKTuYdB47EdRFRQMOE/EAbiG1MJW77/w1b3P7Qx7JSrVJs8PfwOLOVcKQCHErIwCTyPbag==} @@ -1043,20 +1040,20 @@ packages: '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} '@types/jasmine@5.1.8': resolution: {integrity: sha512-u7/CnvRdh6AaaIzYjCgUuVbREFgulhX05Qtf6ZtW+aOcjCKKVvKgpkPYJBFTZSHtFBYimzU4zP0V2vrEsq9Wcg==} - '@types/node@24.0.1': - resolution: {integrity: sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw==} + '@types/node@24.0.3': + resolution: {integrity: sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==} - '@vitejs/plugin-basic-ssl@2.0.0': - resolution: {integrity: sha512-gc9Tjg8bUxBVSTzeWT3Njc0Cl3PakHFKdNfABnZWiUgbxqmHDEn7uECv3fHVylxoYgNzAcmU7ZrILz+BwSo3sA==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + '@vitejs/plugin-basic-ssl@1.2.0': + resolution: {integrity: sha512-mkQnxTkcldAzIsomk1UuLfAu9n+kpQ3JbHcpCp7d2Oo6ITtji8pHS3QToOWjhPFvNQSnhlkAjmGbhv2QvwO/7Q==} + engines: {node: '>=14.21.3'} peerDependencies: - vite: ^6.0.0 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 '@yarnpkg/lockfile@1.1.0': resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} @@ -1115,18 +1112,24 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + base64id@2.0.0: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - beasties@0.3.4: - resolution: {integrity: sha512-NmzN1zN1cvGccXFyZ73335+ASXwBlVWcUPssiUDIlFdfyatHPRRufjCd5w8oPaQPvVnf9ELklaCGb1gi9FBwIw==} + beasties@0.3.2: + resolution: {integrity: sha512-p4AF8uYzm9Fwu8m/hSVTCPXrRBPmB34hQpHsec2KOaR9CZmgoU8IOv4Cvwq4hgz2p4hLMNbsdNl5XeA6XbAQwA==} engines: {node: '>=14.0.0'} binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + body-parser@1.20.3: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -1152,6 +1155,9 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -1171,9 +1177,9 @@ packages: caniuse-lite@1.0.30001723: resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==} - chalk@5.4.1: - resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} @@ -1194,6 +1200,10 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} @@ -1217,9 +1227,9 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - cliui@9.0.1: - resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} - engines: {node: '>=20'} + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -1300,6 +1310,9 @@ packages: supports-color: optional: true + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1408,8 +1421,8 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - esbuild@0.25.5: - resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} + esbuild@0.25.4: + resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==} engines: {node: '>=18'} hasBin: true @@ -1439,9 +1452,16 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + fast-uri@3.0.6: resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fdir@6.4.6: resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: @@ -1596,6 +1616,9 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore-walk@7.0.0: resolution: {integrity: sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ==} engines: {node: ^18.17.0 || >=20.5.0} @@ -1650,9 +1673,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} @@ -1662,13 +1685,9 @@ packages: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - - is-unicode-supported@2.1.0: - resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} - engines: {node: '>=18'} + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} isbinaryfile@4.0.10: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} @@ -1783,20 +1802,20 @@ packages: keycloak-js@26.2.0: resolution: {integrity: sha512-CrFcXTN+d6J0V/1v3Zpioys6qHNWE6yUzVVIsCUAmFn9H14GZ0vuYod+lt+SSpMgWGPuneDZBSGBAeLBFuqjsw==} - listr2@8.3.3: - resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} + listr2@8.2.5: + resolution: {integrity: sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==} engines: {node: '>=18.0.0'} - lmdb@3.3.0: - resolution: {integrity: sha512-MgJocUI6QEiSXQBFWLeyo1R7eQj8Rke5dlPxX0KFwli8/bsCxpM/KbXO5y0qmV/5llQ3wpneDWcTYxa+4vn8iQ==} + lmdb@3.2.6: + resolution: {integrity: sha512-SuHqzPl7mYStna8WRotY8XX/EUZBjjv3QyKIByeCLFfC9uXT/OIHByEcA07PzbMfQAM0KYJtLgtpMRlIe5dErQ==} hasBin: true lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - log-symbols@6.0.0: - resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} - engines: {node: '>=18'} + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} @@ -1831,6 +1850,10 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -1848,6 +1871,10 @@ packages: engines: {node: '>=4.0.0'} hasBin: true + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + mimic-function@5.0.1: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} @@ -1997,9 +2024,9 @@ packages: resolution: {integrity: sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==} engines: {node: ^18.17.0 || >=20.5.0} - npm-packlist@10.0.0: - resolution: {integrity: sha512-rht9U6nS8WOBDc53eipZNPo5qkAV4X2rhKE2Oj1DYUQ3DieXfj0mKkVmjnf3iuNdtMd8WfLdi2L6ASkD/8a+Kg==} - engines: {node: ^20.17.0 || >=22.9.0} + npm-packlist@9.0.0: + resolution: {integrity: sha512-8qSayfmHJQTx3nJWYbbUmflpyarbLMBc6LCAjYsiGtXxDB68HaZpb8re6zeaLGxZzDuMdhsg70jryJe+RrItVQ==} + engines: {node: ^18.17.0 || >=20.5.0} npm-pick-manifest@10.0.0: resolution: {integrity: sha512-r4fFa4FqYY8xaM7fHecQ9Z2nE9hgNfJR+EmoKv0+chvzWkBcORX3r0FpTByP+CbOVJDladMXnPQGVN8PBLGuTQ==} @@ -2031,13 +2058,17 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + onetime@7.0.0: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} - ora@8.2.0: - resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} - engines: {node: '>=18'} + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} ordered-binary@1.5.3: resolution: {integrity: sha512-oGFr3T+pYdTGJ+YFEILMpS3es+GiIbs9h/XQrclBXUtd44ey7XwfsMzM31f64I1SQOawDoDr/D823kNCADI8TA==} @@ -2053,13 +2084,13 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - pacote@21.0.0: - resolution: {integrity: sha512-lcqexq73AMv6QNLo7SOpz0JJoaGdS3rBFgF122NZVl1bApo2mfu+XzUBU/X/XsiJu+iUmKpekRayqQYAs+PhkA==} - engines: {node: ^20.17.0 || >=22.9.0} + pacote@20.0.0: + resolution: {integrity: sha512-pRjC5UFwZCgx9kUFDVM9YEahv4guZ1nSLqwmWiLUnDbGsjs+U5w7z6Uc8HNR1a6x8qnu5y9xtGE6D1uAuYz+0A==} + engines: {node: ^18.17.0 || >=20.5.0} hasBin: true - parse5-html-rewriting-stream@7.1.0: - resolution: {integrity: sha512-2ifK6Jb+ONoqOy5f+cYHsqvx1obHQdvIk13Jmt/5ezxP0U9p+fqd+R6O73KblGswyuzBYfetmsfK9ThMgnuPPg==} + parse5-html-rewriting-stream@7.0.0: + resolution: {integrity: sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==} parse5-sax-parser@7.0.0: resolution: {integrity: sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==} @@ -2097,15 +2128,14 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} - piscina@5.0.0: - resolution: {integrity: sha512-R+arufwL7sZvGjAhSMK3TfH55YdGOqhpKXkcwQJr432AAnJX/xxX19PA4QisrmJ+BTTfZVggaz6HexbkQq1l1Q==} - engines: {node: '>=18.x'} + piscina@4.8.0: + resolution: {integrity: sha512-EZJb+ZxDrQf3dihsUL7p42pjNyrNIFJCrRHPMgxu/svsj+P3xS3fuEWp7k2+rfsavfl1N0G29b1HGs7J0m8rZA==} postcss-media-query-parser@0.2.3: resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} - postcss@8.5.5: - resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==} + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} proc-log@5.0.0: @@ -2127,6 +2157,9 @@ packages: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -2135,6 +2168,10 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -2162,6 +2199,10 @@ packages: engines: {node: '>= 0.4'} hasBin: true + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -2170,6 +2211,10 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} @@ -2178,18 +2223,27 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.40.2: - resolution: {integrity: sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==} + rollup@4.34.8: + resolution: {integrity: sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + run-script-os@1.1.6: resolution: {integrity: sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw==} hasBin: true + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-regex-test@1.1.0: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} @@ -2197,8 +2251,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.88.0: - resolution: {integrity: sha512-sF6TWQqjFvr4JILXzG4ucGOLELkESHL+I5QJhh7CNaE+Yge0SI+ehCatsXhJ7ymU1hAFcIS3/PBpjdIbXoyVbg==} + sass@1.85.0: + resolution: {integrity: sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==} engines: {node: '>=14.0.0'} hasBin: true @@ -2206,6 +2260,11 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -2238,6 +2297,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -2319,10 +2381,6 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - stdin-discarder@0.2.2: - resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} - engines: {node: '>=18'} - streamroller@3.1.5: resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==} engines: {node: '>=8.0'} @@ -2339,6 +2397,9 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -2355,6 +2416,10 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} @@ -2363,10 +2428,6 @@ packages: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} - tinyglobby@0.2.13: - resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.14: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} @@ -2436,6 +2497,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -2451,8 +2515,8 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite@6.3.5: - resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} + vite@6.2.7: + resolution: {integrity: sha512-qg3LkeuinTrZoJHHF94coSaTfIPyBYoywp+ys4qu20oSJFbKMYoIJo0FWJT9q6Vp49l6z9IsJRbHdcGtiKbGoQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: @@ -2499,6 +2563,9 @@ packages: resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} engines: {node: '>=10.13.0'} + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + weak-lru-cache@1.2.2: resolution: {integrity: sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==} @@ -2569,10 +2636,6 @@ packages: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} - yargs-parser@22.0.0: - resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} - yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} @@ -2581,10 +2644,6 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} - yargs@18.0.0: - resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} - engines: {node: ^20.19.0 || ^22.12.0 || >=23} - yoctocolors-cjs@2.1.2: resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} engines: {node: '>=18'} @@ -2599,72 +2658,69 @@ snapshots: '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - '@angular-devkit/architect@0.2000.2(chokidar@4.0.3)': + '@angular-devkit/architect@0.1902.15(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 20.0.2(chokidar@4.0.3) - rxjs: 7.8.2 + '@angular-devkit/core': 19.2.15(chokidar@4.0.3) + rxjs: 7.8.1 transitivePeerDependencies: - chokidar - '@angular-devkit/core@20.0.2(chokidar@4.0.3)': + '@angular-devkit/core@19.2.15(chokidar@4.0.3)': dependencies: ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) jsonc-parser: 3.3.1 picomatch: 4.0.2 - rxjs: 7.8.2 + rxjs: 7.8.1 source-map: 0.7.4 optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/schematics@20.0.2(chokidar@4.0.3)': + '@angular-devkit/schematics@19.2.15(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 20.0.2(chokidar@4.0.3) + '@angular-devkit/core': 19.2.15(chokidar@4.0.3) jsonc-parser: 3.3.1 magic-string: 0.30.17 - ora: 8.2.0 - rxjs: 7.8.2 + ora: 5.4.1 + rxjs: 7.8.1 transitivePeerDependencies: - chokidar - '@angular/build@20.0.2(@angular/compiler-cli@20.0.3(@angular/compiler@20.0.3)(typescript@5.8.3))(@angular/compiler@20.0.3)(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(@types/node@24.0.1)(chokidar@4.0.3)(karma@6.4.4)(postcss@8.5.5)(tslib@2.8.1)(typescript@5.8.3)': + '@angular/build@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(karma@6.4.4)(postcss@8.5.6)(typescript@5.8.3)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.2000.2(chokidar@4.0.3) - '@angular/compiler': 20.0.3 - '@angular/compiler-cli': 20.0.3(@angular/compiler@20.0.3)(typescript@5.8.3) - '@babel/core': 7.27.1 - '@babel/helper-annotate-as-pure': 7.27.1 + '@angular-devkit/architect': 0.1902.15(chokidar@4.0.3) + '@angular/compiler': 19.2.14 + '@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3) + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-split-export-declaration': 7.24.7 - '@inquirer/confirm': 5.1.10(@types/node@24.0.1) - '@vitejs/plugin-basic-ssl': 2.0.0(vite@6.3.5(@types/node@24.0.1)(sass@1.88.0)) - beasties: 0.3.4 + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.10) + '@inquirer/confirm': 5.1.6(@types/node@24.0.3) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.2.7(@types/node@24.0.3)(sass@1.85.0)) + beasties: 0.3.2 browserslist: 4.25.0 - esbuild: 0.25.5 + esbuild: 0.25.4 + fast-glob: 3.3.3 https-proxy-agent: 7.0.6 istanbul-lib-instrument: 6.0.3 - jsonc-parser: 3.3.1 - listr2: 8.3.3 + listr2: 8.2.5 magic-string: 0.30.17 mrmime: 2.0.1 - parse5-html-rewriting-stream: 7.1.0 + parse5-html-rewriting-stream: 7.0.0 picomatch: 4.0.2 - piscina: 5.0.0 - rollup: 4.40.2 - sass: 1.88.0 - semver: 7.7.2 + piscina: 4.8.0 + rollup: 4.34.8 + sass: 1.85.0 + semver: 7.7.1 source-map-support: 0.5.21 - tinyglobby: 0.2.13 - tslib: 2.8.1 typescript: 5.8.3 - vite: 6.3.5(@types/node@24.0.1)(sass@1.88.0) + vite: 6.2.7(@types/node@24.0.3)(sass@1.85.0) watchpack: 2.4.2 optionalDependencies: - '@angular/core': 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) - '@angular/platform-browser': 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)) karma: 6.4.4 - lmdb: 3.3.0 - postcss: 8.5.5 + lmdb: 3.2.6 + postcss: 8.5.6 transitivePeerDependencies: - '@types/node' - chokidar @@ -2678,82 +2734,80 @@ snapshots: - tsx - yaml - '@angular/cli@20.0.2(@types/node@24.0.1)(chokidar@4.0.3)': + '@angular/cli@19.2.15(@types/node@24.0.3)(chokidar@4.0.3)': dependencies: - '@angular-devkit/architect': 0.2000.2(chokidar@4.0.3) - '@angular-devkit/core': 20.0.2(chokidar@4.0.3) - '@angular-devkit/schematics': 20.0.2(chokidar@4.0.3) - '@inquirer/prompts': 7.5.1(@types/node@24.0.1) - '@listr2/prompt-adapter-inquirer': 2.0.22(@inquirer/prompts@7.5.1(@types/node@24.0.1)) - '@schematics/angular': 20.0.2(chokidar@4.0.3) + '@angular-devkit/architect': 0.1902.15(chokidar@4.0.3) + '@angular-devkit/core': 19.2.15(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.15(chokidar@4.0.3) + '@inquirer/prompts': 7.3.2(@types/node@24.0.3) + '@listr2/prompt-adapter-inquirer': 2.0.18(@inquirer/prompts@7.3.2(@types/node@24.0.3)) + '@schematics/angular': 19.2.15(chokidar@4.0.3) '@yarnpkg/lockfile': 1.1.0 ini: 5.0.0 jsonc-parser: 3.3.1 - listr2: 8.3.3 + listr2: 8.2.5 npm-package-arg: 12.0.2 npm-pick-manifest: 10.0.0 - pacote: 21.0.0 + pacote: 20.0.0 resolve: 1.22.10 - semver: 7.7.2 + semver: 7.7.1 + symbol-observable: 4.0.0 yargs: 17.7.2 transitivePeerDependencies: - '@types/node' - chokidar - supports-color - '@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)': + '@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)': dependencies: - '@angular/core': 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/compiler-cli@20.0.3(@angular/compiler@20.0.3)(typescript@5.8.3)': + '@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3)': dependencies: - '@angular/compiler': 20.0.3 - '@babel/core': 7.27.4 + '@angular/compiler': 19.2.14 + '@babel/core': 7.26.9 '@jridgewell/sourcemap-codec': 1.5.0 chokidar: 4.0.3 convert-source-map: 1.9.0 reflect-metadata: 0.2.2 semver: 7.7.2 tslib: 2.8.1 - yargs: 18.0.0 - optionalDependencies: typescript: 5.8.3 + yargs: 17.7.2 transitivePeerDependencies: - supports-color - '@angular/compiler@20.0.3': + '@angular/compiler@19.2.14': dependencies: tslib: 2.8.1 - '@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)': + '@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)': dependencies: rxjs: 7.8.2 tslib: 2.8.1 - optionalDependencies: - '@angular/compiler': 20.0.3 zone.js: 0.15.1 - '@angular/forms@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': + '@angular/forms@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': dependencies: - '@angular/common': 20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) - '@angular/core': 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) - '@angular/platform-browser': 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)) + '@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/platform-browser': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))': + '@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))': dependencies: - '@angular/common': 20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) - '@angular/core': 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) tslib: 2.8.1 - '@angular/router@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': + '@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': dependencies: - '@angular/common': 20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) - '@angular/core': 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) - '@angular/platform-browser': 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)) + '@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/platform-browser': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)) rxjs: 7.8.2 tslib: 2.8.1 @@ -2765,13 +2819,33 @@ snapshots: '@babel/compat-data@7.27.5': {} - '@babel/core@7.27.1': + '@babel/core@7.26.10': dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.27.1 '@babel/generator': 7.27.5 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.1) + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.26.10) + '@babel/helpers': 7.27.6 + '@babel/parser': 7.27.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/core@7.26.9': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.26.9) '@babel/helpers': 7.27.6 '@babel/parser': 7.27.5 '@babel/template': 7.27.2 @@ -2813,7 +2887,7 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 - '@babel/helper-annotate-as-pure@7.27.1': + '@babel/helper-annotate-as-pure@7.25.9': dependencies: '@babel/types': 7.27.6 @@ -2832,9 +2906,18 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.1)': + '@babel/helper-module-transforms@7.27.3(@babel/core@7.26.10)': dependencies: - '@babel/core': 7.27.1 + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.27.3(@babel/core@7.26.9)': + dependencies: + '@babel/core': 7.26.9 '@babel/helper-module-imports': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 '@babel/traverse': 7.27.4 @@ -2850,6 +2933,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-split-export-declaration@7.24.7': dependencies: '@babel/types': 7.27.6 @@ -2869,6 +2954,11 @@ snapshots: dependencies: '@babel/types': 7.27.6 + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -2894,109 +2984,109 @@ snapshots: '@colors/colors@1.5.0': {} - '@esbuild/aix-ppc64@0.25.5': + '@esbuild/aix-ppc64@0.25.4': optional: true - '@esbuild/android-arm64@0.25.5': + '@esbuild/android-arm64@0.25.4': optional: true - '@esbuild/android-arm@0.25.5': + '@esbuild/android-arm@0.25.4': optional: true - '@esbuild/android-x64@0.25.5': + '@esbuild/android-x64@0.25.4': optional: true - '@esbuild/darwin-arm64@0.25.5': + '@esbuild/darwin-arm64@0.25.4': optional: true - '@esbuild/darwin-x64@0.25.5': + '@esbuild/darwin-x64@0.25.4': optional: true - '@esbuild/freebsd-arm64@0.25.5': + '@esbuild/freebsd-arm64@0.25.4': optional: true - '@esbuild/freebsd-x64@0.25.5': + '@esbuild/freebsd-x64@0.25.4': optional: true - '@esbuild/linux-arm64@0.25.5': + '@esbuild/linux-arm64@0.25.4': optional: true - '@esbuild/linux-arm@0.25.5': + '@esbuild/linux-arm@0.25.4': optional: true - '@esbuild/linux-ia32@0.25.5': + '@esbuild/linux-ia32@0.25.4': optional: true - '@esbuild/linux-loong64@0.25.5': + '@esbuild/linux-loong64@0.25.4': optional: true - '@esbuild/linux-mips64el@0.25.5': + '@esbuild/linux-mips64el@0.25.4': optional: true - '@esbuild/linux-ppc64@0.25.5': + '@esbuild/linux-ppc64@0.25.4': optional: true - '@esbuild/linux-riscv64@0.25.5': + '@esbuild/linux-riscv64@0.25.4': optional: true - '@esbuild/linux-s390x@0.25.5': + '@esbuild/linux-s390x@0.25.4': optional: true - '@esbuild/linux-x64@0.25.5': + '@esbuild/linux-x64@0.25.4': optional: true - '@esbuild/netbsd-arm64@0.25.5': + '@esbuild/netbsd-arm64@0.25.4': optional: true - '@esbuild/netbsd-x64@0.25.5': + '@esbuild/netbsd-x64@0.25.4': optional: true - '@esbuild/openbsd-arm64@0.25.5': + '@esbuild/openbsd-arm64@0.25.4': optional: true - '@esbuild/openbsd-x64@0.25.5': + '@esbuild/openbsd-x64@0.25.4': optional: true - '@esbuild/sunos-x64@0.25.5': + '@esbuild/sunos-x64@0.25.4': optional: true - '@esbuild/win32-arm64@0.25.5': + '@esbuild/win32-arm64@0.25.4': optional: true - '@esbuild/win32-ia32@0.25.5': + '@esbuild/win32-ia32@0.25.4': optional: true - '@esbuild/win32-x64@0.25.5': + '@esbuild/win32-x64@0.25.4': optional: true - '@inquirer/checkbox@4.1.8(@types/node@24.0.1)': + '@inquirer/checkbox@4.1.8(@types/node@24.0.3)': dependencies: - '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/core': 10.1.13(@types/node@24.0.3) '@inquirer/figures': 1.0.12 - '@inquirer/type': 3.0.7(@types/node@24.0.1) + '@inquirer/type': 3.0.7(@types/node@24.0.3) ansi-escapes: 4.3.2 yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 - '@inquirer/confirm@5.1.10(@types/node@24.0.1)': + '@inquirer/confirm@5.1.12(@types/node@24.0.3)': dependencies: - '@inquirer/core': 10.1.13(@types/node@24.0.1) - '@inquirer/type': 3.0.7(@types/node@24.0.1) + '@inquirer/core': 10.1.13(@types/node@24.0.3) + '@inquirer/type': 3.0.7(@types/node@24.0.3) optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 - '@inquirer/confirm@5.1.12(@types/node@24.0.1)': + '@inquirer/confirm@5.1.6(@types/node@24.0.3)': dependencies: - '@inquirer/core': 10.1.13(@types/node@24.0.1) - '@inquirer/type': 3.0.7(@types/node@24.0.1) + '@inquirer/core': 10.1.13(@types/node@24.0.3) + '@inquirer/type': 3.0.7(@types/node@24.0.3) optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 - '@inquirer/core@10.1.13(@types/node@24.0.1)': + '@inquirer/core@10.1.13(@types/node@24.0.3)': dependencies: '@inquirer/figures': 1.0.12 - '@inquirer/type': 3.0.7(@types/node@24.0.1) + '@inquirer/type': 3.0.7(@types/node@24.0.3) ansi-escapes: 4.3.2 cli-width: 4.1.0 mute-stream: 2.0.0 @@ -3004,97 +3094,97 @@ snapshots: wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 - '@inquirer/editor@4.2.13(@types/node@24.0.1)': + '@inquirer/editor@4.2.13(@types/node@24.0.3)': dependencies: - '@inquirer/core': 10.1.13(@types/node@24.0.1) - '@inquirer/type': 3.0.7(@types/node@24.0.1) + '@inquirer/core': 10.1.13(@types/node@24.0.3) + '@inquirer/type': 3.0.7(@types/node@24.0.3) external-editor: 3.1.0 optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 - '@inquirer/expand@4.0.15(@types/node@24.0.1)': + '@inquirer/expand@4.0.15(@types/node@24.0.3)': dependencies: - '@inquirer/core': 10.1.13(@types/node@24.0.1) - '@inquirer/type': 3.0.7(@types/node@24.0.1) + '@inquirer/core': 10.1.13(@types/node@24.0.3) + '@inquirer/type': 3.0.7(@types/node@24.0.3) yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 '@inquirer/figures@1.0.12': {} - '@inquirer/input@4.1.12(@types/node@24.0.1)': + '@inquirer/input@4.1.12(@types/node@24.0.3)': dependencies: - '@inquirer/core': 10.1.13(@types/node@24.0.1) - '@inquirer/type': 3.0.7(@types/node@24.0.1) + '@inquirer/core': 10.1.13(@types/node@24.0.3) + '@inquirer/type': 3.0.7(@types/node@24.0.3) optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 - '@inquirer/number@3.0.15(@types/node@24.0.1)': + '@inquirer/number@3.0.15(@types/node@24.0.3)': dependencies: - '@inquirer/core': 10.1.13(@types/node@24.0.1) - '@inquirer/type': 3.0.7(@types/node@24.0.1) + '@inquirer/core': 10.1.13(@types/node@24.0.3) + '@inquirer/type': 3.0.7(@types/node@24.0.3) optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 - '@inquirer/password@4.0.15(@types/node@24.0.1)': + '@inquirer/password@4.0.15(@types/node@24.0.3)': dependencies: - '@inquirer/core': 10.1.13(@types/node@24.0.1) - '@inquirer/type': 3.0.7(@types/node@24.0.1) + '@inquirer/core': 10.1.13(@types/node@24.0.3) + '@inquirer/type': 3.0.7(@types/node@24.0.3) ansi-escapes: 4.3.2 optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 - '@inquirer/prompts@7.5.1(@types/node@24.0.1)': + '@inquirer/prompts@7.3.2(@types/node@24.0.3)': dependencies: - '@inquirer/checkbox': 4.1.8(@types/node@24.0.1) - '@inquirer/confirm': 5.1.12(@types/node@24.0.1) - '@inquirer/editor': 4.2.13(@types/node@24.0.1) - '@inquirer/expand': 4.0.15(@types/node@24.0.1) - '@inquirer/input': 4.1.12(@types/node@24.0.1) - '@inquirer/number': 3.0.15(@types/node@24.0.1) - '@inquirer/password': 4.0.15(@types/node@24.0.1) - '@inquirer/rawlist': 4.1.3(@types/node@24.0.1) - '@inquirer/search': 3.0.15(@types/node@24.0.1) - '@inquirer/select': 4.2.3(@types/node@24.0.1) + '@inquirer/checkbox': 4.1.8(@types/node@24.0.3) + '@inquirer/confirm': 5.1.12(@types/node@24.0.3) + '@inquirer/editor': 4.2.13(@types/node@24.0.3) + '@inquirer/expand': 4.0.15(@types/node@24.0.3) + '@inquirer/input': 4.1.12(@types/node@24.0.3) + '@inquirer/number': 3.0.15(@types/node@24.0.3) + '@inquirer/password': 4.0.15(@types/node@24.0.3) + '@inquirer/rawlist': 4.1.3(@types/node@24.0.3) + '@inquirer/search': 3.0.15(@types/node@24.0.3) + '@inquirer/select': 4.2.3(@types/node@24.0.3) optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 - '@inquirer/rawlist@4.1.3(@types/node@24.0.1)': + '@inquirer/rawlist@4.1.3(@types/node@24.0.3)': dependencies: - '@inquirer/core': 10.1.13(@types/node@24.0.1) - '@inquirer/type': 3.0.7(@types/node@24.0.1) + '@inquirer/core': 10.1.13(@types/node@24.0.3) + '@inquirer/type': 3.0.7(@types/node@24.0.3) yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 - '@inquirer/search@3.0.15(@types/node@24.0.1)': + '@inquirer/search@3.0.15(@types/node@24.0.3)': dependencies: - '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/core': 10.1.13(@types/node@24.0.3) '@inquirer/figures': 1.0.12 - '@inquirer/type': 3.0.7(@types/node@24.0.1) + '@inquirer/type': 3.0.7(@types/node@24.0.3) yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 - '@inquirer/select@4.2.3(@types/node@24.0.1)': + '@inquirer/select@4.2.3(@types/node@24.0.3)': dependencies: - '@inquirer/core': 10.1.13(@types/node@24.0.1) + '@inquirer/core': 10.1.13(@types/node@24.0.3) '@inquirer/figures': 1.0.12 - '@inquirer/type': 3.0.7(@types/node@24.0.1) + '@inquirer/type': 3.0.7(@types/node@24.0.3) ansi-escapes: 4.3.2 yoctocolors-cjs: 2.1.2 optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 '@inquirer/type@1.5.5': dependencies: mute-stream: 1.0.0 - '@inquirer/type@3.0.7(@types/node@24.0.1)': + '@inquirer/type@3.0.7(@types/node@24.0.3)': optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 '@isaacs/cliui@8.0.2': dependencies: @@ -3128,30 +3218,27 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@listr2/prompt-adapter-inquirer@2.0.22(@inquirer/prompts@7.5.1(@types/node@24.0.1))': + '@listr2/prompt-adapter-inquirer@2.0.18(@inquirer/prompts@7.3.2(@types/node@24.0.3))': dependencies: - '@inquirer/prompts': 7.5.1(@types/node@24.0.1) + '@inquirer/prompts': 7.3.2(@types/node@24.0.3) '@inquirer/type': 1.5.5 - '@lmdb/lmdb-darwin-arm64@3.3.0': + '@lmdb/lmdb-darwin-arm64@3.2.6': optional: true - '@lmdb/lmdb-darwin-x64@3.3.0': + '@lmdb/lmdb-darwin-x64@3.2.6': optional: true - '@lmdb/lmdb-linux-arm64@3.3.0': + '@lmdb/lmdb-linux-arm64@3.2.6': optional: true - '@lmdb/lmdb-linux-arm@3.3.0': + '@lmdb/lmdb-linux-arm@3.2.6': optional: true - '@lmdb/lmdb-linux-x64@3.3.0': + '@lmdb/lmdb-linux-x64@3.2.6': optional: true - '@lmdb/lmdb-win32-arm64@3.3.0': - optional: true - - '@lmdb/lmdb-win32-x64@3.3.0': + '@lmdb/lmdb-win32-x64@3.2.6': optional: true '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': @@ -3240,6 +3327,18 @@ snapshots: '@napi-rs/nice-win32-x64-msvc': 1.0.1 optional: true + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + '@npmcli/agent@3.0.0': dependencies: agent-base: 7.1.3 @@ -3252,7 +3351,7 @@ snapshots: '@npmcli/fs@4.0.0': dependencies: - semver: 7.7.2 + semver: 7.7.1 '@npmcli/git@6.0.3': dependencies: @@ -3262,7 +3361,7 @@ snapshots: npm-pick-manifest: 10.0.0 proc-log: 5.0.0 promise-retry: 2.0.1 - semver: 7.7.2 + semver: 7.7.1 which: 5.0.0 '@npmcli/installed-package-contents@3.0.0': @@ -3279,7 +3378,7 @@ snapshots: hosted-git-info: 8.1.0 json-parse-even-better-errors: 4.0.0 proc-log: 5.0.0 - semver: 7.7.2 + semver: 7.7.1 validate-npm-package-license: 3.0.4 '@npmcli/promise-spawn@8.0.2': @@ -3363,70 +3462,67 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@rollup/rollup-android-arm-eabi@4.40.2': + '@rollup/rollup-android-arm-eabi@4.34.8': optional: true - '@rollup/rollup-android-arm64@4.40.2': + '@rollup/rollup-android-arm64@4.34.8': optional: true - '@rollup/rollup-darwin-arm64@4.40.2': + '@rollup/rollup-darwin-arm64@4.34.8': optional: true - '@rollup/rollup-darwin-x64@4.40.2': + '@rollup/rollup-darwin-x64@4.34.8': optional: true - '@rollup/rollup-freebsd-arm64@4.40.2': + '@rollup/rollup-freebsd-arm64@4.34.8': optional: true - '@rollup/rollup-freebsd-x64@4.40.2': + '@rollup/rollup-freebsd-x64@4.34.8': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.40.2': + '@rollup/rollup-linux-arm-gnueabihf@4.34.8': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.40.2': + '@rollup/rollup-linux-arm-musleabihf@4.34.8': optional: true - '@rollup/rollup-linux-arm64-gnu@4.40.2': + '@rollup/rollup-linux-arm64-gnu@4.34.8': optional: true - '@rollup/rollup-linux-arm64-musl@4.40.2': + '@rollup/rollup-linux-arm64-musl@4.34.8': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.40.2': + '@rollup/rollup-linux-loongarch64-gnu@4.34.8': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.40.2': + '@rollup/rollup-linux-powerpc64le-gnu@4.34.8': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.40.2': + '@rollup/rollup-linux-riscv64-gnu@4.34.8': optional: true - '@rollup/rollup-linux-riscv64-musl@4.40.2': + '@rollup/rollup-linux-s390x-gnu@4.34.8': optional: true - '@rollup/rollup-linux-s390x-gnu@4.40.2': + '@rollup/rollup-linux-x64-gnu@4.34.8': optional: true - '@rollup/rollup-linux-x64-gnu@4.40.2': + '@rollup/rollup-linux-x64-musl@4.34.8': optional: true - '@rollup/rollup-linux-x64-musl@4.40.2': + '@rollup/rollup-win32-arm64-msvc@4.34.8': optional: true - '@rollup/rollup-win32-arm64-msvc@4.40.2': + '@rollup/rollup-win32-ia32-msvc@4.34.8': optional: true - '@rollup/rollup-win32-ia32-msvc@4.40.2': + '@rollup/rollup-win32-x64-msvc@4.34.8': optional: true - '@rollup/rollup-win32-x64-msvc@4.40.2': - optional: true - - '@schematics/angular@20.0.2(chokidar@4.0.3)': + '@schematics/angular@19.2.15(chokidar@4.0.3)': dependencies: - '@angular-devkit/core': 20.0.2(chokidar@4.0.3) - '@angular-devkit/schematics': 20.0.2(chokidar@4.0.3) + '@angular-devkit/core': 19.2.15(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.15(chokidar@4.0.3) jsonc-parser: 3.3.1 transitivePeerDependencies: - chokidar @@ -3474,19 +3570,19 @@ snapshots: '@types/cors@2.8.19': dependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 - '@types/estree@1.0.7': {} + '@types/estree@1.0.6': {} '@types/jasmine@5.1.8': {} - '@types/node@24.0.1': + '@types/node@24.0.3': dependencies: undici-types: 7.8.0 - '@vitejs/plugin-basic-ssl@2.0.0(vite@6.3.5(@types/node@24.0.1)(sass@1.88.0))': + '@vitejs/plugin-basic-ssl@1.2.0(vite@6.2.7(@types/node@24.0.3)(sass@1.85.0))': dependencies: - vite: 6.3.5(@types/node@24.0.1)(sass@1.88.0) + vite: 6.2.7(@types/node@24.0.3)(sass@1.85.0) '@yarnpkg/lockfile@1.1.0': {} @@ -3535,9 +3631,11 @@ snapshots: balanced-match@1.0.2: {} + base64-js@1.5.1: {} + base64id@2.0.0: {} - beasties@0.3.4: + beasties@0.3.2: dependencies: css-select: 5.1.0 css-what: 6.1.0 @@ -3545,11 +3643,17 @@ snapshots: domhandler: 5.0.3 htmlparser2: 10.0.0 picocolors: 1.1.1 - postcss: 8.5.5 + postcss: 8.5.6 postcss-media-query-parser: 0.2.3 binary-extensions@2.3.0: {} + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + body-parser@1.20.3: dependencies: bytes: 3.1.2 @@ -3591,6 +3695,11 @@ snapshots: buffer-from@1.1.2: {} + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + bytes@3.1.2: {} cacache@19.0.1: @@ -3620,7 +3729,10 @@ snapshots: caniuse-lite@1.0.30001723: {} - chalk@5.4.1: {} + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 chardet@0.7.0: {} @@ -3644,6 +3756,10 @@ snapshots: chownr@3.0.0: {} + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -3669,11 +3785,7 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - cliui@9.0.1: - dependencies: - string-width: 7.2.0 - strip-ansi: 7.1.0 - wrap-ansi: 9.0.0 + clone@1.0.4: {} color-convert@2.0.1: dependencies: @@ -3739,6 +3851,10 @@ snapshots: dependencies: ms: 2.1.3 + defaults@1.0.4: + dependencies: + clone: 1.0.4 + depd@2.0.0: {} destroy@1.2.0: {} @@ -3806,7 +3922,7 @@ snapshots: engine.io@6.6.4: dependencies: '@types/cors': 2.8.19 - '@types/node': 24.0.1 + '@types/node': 24.0.3 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -3844,33 +3960,33 @@ snapshots: dependencies: es-errors: 1.3.0 - esbuild@0.25.5: + esbuild@0.25.4: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.5 - '@esbuild/android-arm': 0.25.5 - '@esbuild/android-arm64': 0.25.5 - '@esbuild/android-x64': 0.25.5 - '@esbuild/darwin-arm64': 0.25.5 - '@esbuild/darwin-x64': 0.25.5 - '@esbuild/freebsd-arm64': 0.25.5 - '@esbuild/freebsd-x64': 0.25.5 - '@esbuild/linux-arm': 0.25.5 - '@esbuild/linux-arm64': 0.25.5 - '@esbuild/linux-ia32': 0.25.5 - '@esbuild/linux-loong64': 0.25.5 - '@esbuild/linux-mips64el': 0.25.5 - '@esbuild/linux-ppc64': 0.25.5 - '@esbuild/linux-riscv64': 0.25.5 - '@esbuild/linux-s390x': 0.25.5 - '@esbuild/linux-x64': 0.25.5 - '@esbuild/netbsd-arm64': 0.25.5 - '@esbuild/netbsd-x64': 0.25.5 - '@esbuild/openbsd-arm64': 0.25.5 - '@esbuild/openbsd-x64': 0.25.5 - '@esbuild/sunos-x64': 0.25.5 - '@esbuild/win32-arm64': 0.25.5 - '@esbuild/win32-ia32': 0.25.5 - '@esbuild/win32-x64': 0.25.5 + '@esbuild/aix-ppc64': 0.25.4 + '@esbuild/android-arm': 0.25.4 + '@esbuild/android-arm64': 0.25.4 + '@esbuild/android-x64': 0.25.4 + '@esbuild/darwin-arm64': 0.25.4 + '@esbuild/darwin-x64': 0.25.4 + '@esbuild/freebsd-arm64': 0.25.4 + '@esbuild/freebsd-x64': 0.25.4 + '@esbuild/linux-arm': 0.25.4 + '@esbuild/linux-arm64': 0.25.4 + '@esbuild/linux-ia32': 0.25.4 + '@esbuild/linux-loong64': 0.25.4 + '@esbuild/linux-mips64el': 0.25.4 + '@esbuild/linux-ppc64': 0.25.4 + '@esbuild/linux-riscv64': 0.25.4 + '@esbuild/linux-s390x': 0.25.4 + '@esbuild/linux-x64': 0.25.4 + '@esbuild/netbsd-arm64': 0.25.4 + '@esbuild/netbsd-x64': 0.25.4 + '@esbuild/openbsd-arm64': 0.25.4 + '@esbuild/openbsd-x64': 0.25.4 + '@esbuild/sunos-x64': 0.25.4 + '@esbuild/win32-arm64': 0.25.4 + '@esbuild/win32-ia32': 0.25.4 + '@esbuild/win32-x64': 0.25.4 escalade@3.2.0: {} @@ -3892,8 +4008,20 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + fast-uri@3.0.6: {} + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + fdir@6.4.6(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -4064,6 +4192,8 @@ snapshots: safer-buffer: 2.1.2 optional: true + ieee754@1.2.1: {} + ignore-walk@7.0.0: dependencies: minimatch: 9.0.5 @@ -4108,7 +4238,7 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-interactive@2.0.0: {} + is-interactive@1.0.0: {} is-number@7.0.0: {} @@ -4119,9 +4249,7 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - is-unicode-supported@1.3.0: {} - - is-unicode-supported@2.1.0: {} + is-unicode-supported@0.1.0: {} isbinaryfile@4.0.10: {} @@ -4143,11 +4271,11 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.27.1 + '@babel/core': 7.26.10 '@babel/parser': 7.27.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.2 + semver: 7.7.1 transitivePeerDependencies: - supports-color @@ -4258,17 +4386,17 @@ snapshots: - supports-color - utf-8-validate - keycloak-angular@19.0.2(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(keycloak-js@26.2.0): + keycloak-angular@19.0.2(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(keycloak-js@26.2.0): dependencies: - '@angular/common': 20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) - '@angular/core': 20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1) - '@angular/router': 20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@20.0.3(@angular/common@20.0.3(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.0.3(@angular/compiler@20.0.3)(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/router': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) keycloak-js: 26.2.0 tslib: 2.8.1 keycloak-js@26.2.0: {} - listr2@8.3.3: + listr2@8.2.5: dependencies: cli-truncate: 4.0.0 colorette: 2.0.20 @@ -4277,7 +4405,7 @@ snapshots: rfdc: 1.4.1 wrap-ansi: 9.0.0 - lmdb@3.3.0: + lmdb@3.2.6: dependencies: msgpackr: 1.11.4 node-addon-api: 6.1.0 @@ -4285,21 +4413,20 @@ snapshots: ordered-binary: 1.5.3 weak-lru-cache: 1.2.2 optionalDependencies: - '@lmdb/lmdb-darwin-arm64': 3.3.0 - '@lmdb/lmdb-darwin-x64': 3.3.0 - '@lmdb/lmdb-linux-arm': 3.3.0 - '@lmdb/lmdb-linux-arm64': 3.3.0 - '@lmdb/lmdb-linux-x64': 3.3.0 - '@lmdb/lmdb-win32-arm64': 3.3.0 - '@lmdb/lmdb-win32-x64': 3.3.0 + '@lmdb/lmdb-darwin-arm64': 3.2.6 + '@lmdb/lmdb-darwin-x64': 3.2.6 + '@lmdb/lmdb-linux-arm': 3.2.6 + '@lmdb/lmdb-linux-arm64': 3.2.6 + '@lmdb/lmdb-linux-x64': 3.2.6 + '@lmdb/lmdb-win32-x64': 3.2.6 optional: true lodash@4.17.21: {} - log-symbols@6.0.0: + log-symbols@4.1.0: dependencies: - chalk: 5.4.1 - is-unicode-supported: 1.3.0 + chalk: 4.1.2 + is-unicode-supported: 0.1.0 log-update@6.1.0: dependencies: @@ -4353,11 +4480,12 @@ snapshots: media-typer@0.3.0: {} + merge2@1.4.1: {} + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 - optional: true mime-db@1.52.0: {} @@ -4367,6 +4495,8 @@ snapshots: mime@2.6.0: {} + mimic-fn@2.1.0: {} + mimic-function@5.0.1: {} minimatch@3.1.2: @@ -4480,7 +4610,7 @@ snapshots: make-fetch-happen: 14.0.3 nopt: 8.1.0 proc-log: 5.0.0 - semver: 7.7.2 + semver: 7.7.1 tar: 7.4.3 tinyglobby: 0.2.14 which: 5.0.0 @@ -4501,7 +4631,7 @@ snapshots: npm-install-checks@7.1.1: dependencies: - semver: 7.7.2 + semver: 7.7.1 npm-normalize-package-bin@4.0.0: {} @@ -4509,10 +4639,10 @@ snapshots: dependencies: hosted-git-info: 8.1.0 proc-log: 5.0.0 - semver: 7.7.2 + semver: 7.7.1 validate-npm-package-name: 6.0.1 - npm-packlist@10.0.0: + npm-packlist@9.0.0: dependencies: ignore-walk: 7.0.0 @@ -4521,7 +4651,7 @@ snapshots: npm-install-checks: 7.1.1 npm-normalize-package-bin: 4.0.0 npm-package-arg: 12.0.2 - semver: 7.7.2 + semver: 7.7.1 npm-registry-fetch@18.0.2: dependencies: @@ -4556,21 +4686,25 @@ snapshots: dependencies: wrappy: 1.0.2 + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + onetime@7.0.0: dependencies: mimic-function: 5.0.1 - ora@8.2.0: + ora@5.4.1: dependencies: - chalk: 5.4.1 - cli-cursor: 5.0.0 + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 cli-spinners: 2.9.2 - is-interactive: 2.0.0 - is-unicode-supported: 2.1.0 - log-symbols: 6.0.0 - stdin-discarder: 0.2.2 - string-width: 7.2.0 - strip-ansi: 7.1.0 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 ordered-binary@1.5.3: optional: true @@ -4581,7 +4715,7 @@ snapshots: package-json-from-dist@1.0.1: {} - pacote@21.0.0: + pacote@20.0.0: dependencies: '@npmcli/git': 6.0.3 '@npmcli/installed-package-contents': 3.0.0 @@ -4592,7 +4726,7 @@ snapshots: fs-minipass: 3.0.3 minipass: 7.1.2 npm-package-arg: 12.0.2 - npm-packlist: 10.0.0 + npm-packlist: 9.0.0 npm-pick-manifest: 10.0.0 npm-registry-fetch: 18.0.2 proc-log: 5.0.0 @@ -4603,9 +4737,9 @@ snapshots: transitivePeerDependencies: - supports-color - parse5-html-rewriting-stream@7.1.0: + parse5-html-rewriting-stream@7.0.0: dependencies: - entities: 6.0.1 + entities: 4.5.0 parse5: 7.3.0 parse5-sax-parser: 7.0.0 @@ -4636,13 +4770,13 @@ snapshots: picomatch@4.0.2: {} - piscina@5.0.0: + piscina@4.8.0: optionalDependencies: '@napi-rs/nice': 1.0.1 postcss-media-query-parser@0.2.3: {} - postcss@8.5.5: + postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -4663,6 +4797,8 @@ snapshots: dependencies: side-channel: 1.1.0 + queue-microtask@1.2.3: {} + range-parser@1.2.1: {} raw-body@2.5.2: @@ -4672,6 +4808,12 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -4692,6 +4834,11 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -4699,44 +4846,55 @@ snapshots: retry@0.12.0: {} + reusify@1.1.0: {} + rfdc@1.4.1: {} rimraf@3.0.2: dependencies: glob: 7.2.3 - rollup@4.40.2: + rollup@4.34.8: dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.40.2 - '@rollup/rollup-android-arm64': 4.40.2 - '@rollup/rollup-darwin-arm64': 4.40.2 - '@rollup/rollup-darwin-x64': 4.40.2 - '@rollup/rollup-freebsd-arm64': 4.40.2 - '@rollup/rollup-freebsd-x64': 4.40.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.40.2 - '@rollup/rollup-linux-arm-musleabihf': 4.40.2 - '@rollup/rollup-linux-arm64-gnu': 4.40.2 - '@rollup/rollup-linux-arm64-musl': 4.40.2 - '@rollup/rollup-linux-loongarch64-gnu': 4.40.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.40.2 - '@rollup/rollup-linux-riscv64-gnu': 4.40.2 - '@rollup/rollup-linux-riscv64-musl': 4.40.2 - '@rollup/rollup-linux-s390x-gnu': 4.40.2 - '@rollup/rollup-linux-x64-gnu': 4.40.2 - '@rollup/rollup-linux-x64-musl': 4.40.2 - '@rollup/rollup-win32-arm64-msvc': 4.40.2 - '@rollup/rollup-win32-ia32-msvc': 4.40.2 - '@rollup/rollup-win32-x64-msvc': 4.40.2 + '@rollup/rollup-android-arm-eabi': 4.34.8 + '@rollup/rollup-android-arm64': 4.34.8 + '@rollup/rollup-darwin-arm64': 4.34.8 + '@rollup/rollup-darwin-x64': 4.34.8 + '@rollup/rollup-freebsd-arm64': 4.34.8 + '@rollup/rollup-freebsd-x64': 4.34.8 + '@rollup/rollup-linux-arm-gnueabihf': 4.34.8 + '@rollup/rollup-linux-arm-musleabihf': 4.34.8 + '@rollup/rollup-linux-arm64-gnu': 4.34.8 + '@rollup/rollup-linux-arm64-musl': 4.34.8 + '@rollup/rollup-linux-loongarch64-gnu': 4.34.8 + '@rollup/rollup-linux-powerpc64le-gnu': 4.34.8 + '@rollup/rollup-linux-riscv64-gnu': 4.34.8 + '@rollup/rollup-linux-s390x-gnu': 4.34.8 + '@rollup/rollup-linux-x64-gnu': 4.34.8 + '@rollup/rollup-linux-x64-musl': 4.34.8 + '@rollup/rollup-win32-arm64-msvc': 4.34.8 + '@rollup/rollup-win32-ia32-msvc': 4.34.8 + '@rollup/rollup-win32-x64-msvc': 4.34.8 fsevents: 2.3.3 + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + run-script-os@1.1.6: {} + rxjs@7.8.1: + dependencies: + tslib: 2.8.1 + rxjs@7.8.2: dependencies: tslib: 2.8.1 + safe-buffer@5.2.1: {} + safe-regex-test@1.1.0: dependencies: call-bound: 1.0.4 @@ -4745,7 +4903,7 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.88.0: + sass@1.85.0: dependencies: chokidar: 4.0.3 immutable: 5.1.3 @@ -4755,6 +4913,8 @@ snapshots: semver@6.3.1: {} + semver@7.7.1: {} + semver@7.7.2: {} setprototypeof@1.2.0: {} @@ -4793,6 +4953,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + signal-exit@3.0.7: {} + signal-exit@4.1.0: {} sigstore@3.1.0: @@ -4896,8 +5058,6 @@ snapshots: statuses@2.0.1: {} - stdin-discarder@0.2.2: {} - streamroller@3.1.5: dependencies: date-format: 4.0.14 @@ -4924,6 +5084,10 @@ snapshots: get-east-asian-width: 1.3.0 strip-ansi: 7.1.0 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -4938,6 +5102,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + symbol-observable@4.0.0: {} + tar@6.2.1: dependencies: chownr: 2.0.0 @@ -4956,11 +5122,6 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 - tinyglobby@0.2.13: - dependencies: - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 - tinyglobby@0.2.14: dependencies: fdir: 6.4.6(picomatch@4.0.2) @@ -5019,6 +5180,8 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + util-deprecate@1.0.2: {} + utils-merge@1.0.1: {} validate-npm-package-license@3.0.4: @@ -5030,18 +5193,15 @@ snapshots: vary@1.1.2: {} - vite@6.3.5(@types/node@24.0.1)(sass@1.88.0): + vite@6.2.7(@types/node@24.0.3)(sass@1.85.0): dependencies: - esbuild: 0.25.5 - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 - postcss: 8.5.5 - rollup: 4.40.2 - tinyglobby: 0.2.13 + esbuild: 0.25.4 + postcss: 8.5.6 + rollup: 4.34.8 optionalDependencies: - '@types/node': 24.0.1 + '@types/node': 24.0.3 fsevents: 2.3.3 - sass: 1.88.0 + sass: 1.85.0 void-elements@2.0.1: {} @@ -5050,6 +5210,10 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + weak-lru-cache@1.2.2: optional: true @@ -5105,8 +5269,6 @@ snapshots: yargs-parser@21.1.1: {} - yargs-parser@22.0.0: {} - yargs@16.2.0: dependencies: cliui: 7.0.4 @@ -5127,15 +5289,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yargs@18.0.0: - dependencies: - cliui: 9.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - string-width: 7.2.0 - y18n: 5.0.8 - yargs-parser: 22.0.0 - yoctocolors-cjs@2.1.2: {} zone.js@0.15.1: {} -- 2.49.1 From 0baf50a2a295deec75090fd8370aab239f89fcbd Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 17:51:17 +0200 Subject: [PATCH 080/150] Fix downgrade to Angular 19 --- src/Vegasco-Web/angular.json | 16 +- src/Vegasco-Web/package.json | 4 +- src/Vegasco-Web/pnpm-lock.yaml | 3430 ++++++++++++++++++++++++- src/Vegasco-Web/src/app/app.config.ts | 10 +- 4 files changed, 3419 insertions(+), 41 deletions(-) diff --git a/src/Vegasco-Web/angular.json b/src/Vegasco-Web/angular.json index e053d58..e07b206 100644 --- a/src/Vegasco-Web/angular.json +++ b/src/Vegasco-Web/angular.json @@ -15,8 +15,10 @@ "prefix": "app", "architect": { "build": { - "builder": "@angular/build:application", + "builder": "@angular-devkit/build-angular:application", "options": { + "outputPath": "dist/tmp", + "index": "src/index.html", "browser": "src/main.ts", "polyfills": [ "zone.js" @@ -31,7 +33,8 @@ ], "styles": [ "src/styles.scss" - ] + ], + "scripts": [] }, "configurations": { "production": { @@ -70,7 +73,7 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular/build:dev-server", + "builder": "@angular-devkit/build-angular:dev-server", "options": { "proxyConfig": "proxy.config.js" }, @@ -85,10 +88,10 @@ "defaultConfiguration": "development" }, "extract-i18n": { - "builder": "@angular/build:extract-i18n" + "builder": "@angular-devkit/build-angular:extract-i18n" }, "test": { - "builder": "@angular/build:karma", + "builder": "@angular-devkit/build-angular:karma", "options": { "polyfills": [ "zone.js", @@ -104,7 +107,8 @@ ], "styles": [ "src/styles.scss" - ] + ], + "scripts": [] } } } diff --git a/src/Vegasco-Web/package.json b/src/Vegasco-Web/package.json index 0fb3061..1a7decd 100644 --- a/src/Vegasco-Web/package.json +++ b/src/Vegasco-Web/package.json @@ -24,7 +24,7 @@ "zone.js": "~0.15.1" }, "devDependencies": { - "@angular/build": "^19.2.15", + "@angular-devkit/build-angular": "^19.2.15", "@angular/cli": "^19.2.15", "@angular/compiler-cli": "^19.2.14", "@types/jasmine": "~5.1.8", @@ -37,4 +37,4 @@ "run-script-os": "^1.1.6", "typescript": "~5.8.3" } -} +} \ No newline at end of file diff --git a/src/Vegasco-Web/pnpm-lock.yaml b/src/Vegasco-Web/pnpm-lock.yaml index 2777dc5..c2a8b1c 100644 --- a/src/Vegasco-Web/pnpm-lock.yaml +++ b/src/Vegasco-Web/pnpm-lock.yaml @@ -19,16 +19,16 @@ importers: version: 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) '@angular/forms': specifier: ^19.2.14 - version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) '@angular/platform-browser': specifier: ^19.2.14 - version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)) + version: 19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)) '@angular/router': specifier: ^19.2.14 - version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) keycloak-angular: specifier: ^19.0.2 - version: 19.0.2(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(keycloak-js@26.2.0) + version: 19.0.2(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(keycloak-js@26.2.0) rxjs: specifier: ~7.8.2 version: 7.8.2 @@ -39,9 +39,9 @@ importers: specifier: ~0.15.1 version: 0.15.1 devDependencies: - '@angular/build': + '@angular-devkit/build-angular': specifier: ^19.2.15 - version: 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(karma@6.4.4)(postcss@8.5.6)(typescript@5.8.3) + version: 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(typescript@5.8.3)(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)) '@angular/cli': specifier: ^19.2.15 version: 19.2.15(@types/node@24.0.3)(chokidar@4.0.3) @@ -86,6 +86,57 @@ packages: resolution: {integrity: sha512-RbqhStc6ZoRv57ZqLB36VOkBkAdU3nNezCvIs0AJV5V4+vLPMrb0hpIB0sF+9yMlMjWsolnRsj0/Fil+zQG3bw==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/build-angular@19.2.15': + resolution: {integrity: sha512-mqudAcyrSp/E7ZQdQoHfys0/nvQuwyJDaAzj3qL3HUStuUzb5ULNOj2f6sFBo+xYo+/WT8IzmzDN9DCqDgvFaA==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + '@angular/compiler-cli': ^19.0.0 || ^19.2.0-next.0 + '@angular/localize': ^19.0.0 || ^19.2.0-next.0 + '@angular/platform-server': ^19.0.0 || ^19.2.0-next.0 + '@angular/service-worker': ^19.0.0 || ^19.2.0-next.0 + '@angular/ssr': ^19.2.15 + '@web/test-runner': ^0.20.0 + browser-sync: ^3.0.2 + jest: ^29.5.0 + jest-environment-jsdom: ^29.5.0 + karma: ^6.3.0 + ng-packagr: ^19.0.0 || ^19.2.0-next.0 + protractor: ^7.0.0 + tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 + typescript: '>=5.5 <5.9' + peerDependenciesMeta: + '@angular/localize': + optional: true + '@angular/platform-server': + optional: true + '@angular/service-worker': + optional: true + '@angular/ssr': + optional: true + '@web/test-runner': + optional: true + browser-sync: + optional: true + jest: + optional: true + jest-environment-jsdom: + optional: true + karma: + optional: true + ng-packagr: + optional: true + protractor: + optional: true + tailwindcss: + optional: true + + '@angular-devkit/build-webpack@0.1902.15': + resolution: {integrity: sha512-pIfZeizWsViXx8bsMoBLZw7Tl7uFf7bM7hAfmNwk0bb0QGzx5k1BiW6IKWyaG+Dg6U4UCrlNpIiut2b78HwQZw==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + webpack: ^5.30.0 + webpack-dev-server: ^5.0.2 + '@angular-devkit/core@19.2.15': resolution: {integrity: sha512-pU2RZYX6vhd7uLSdLwPnuBcr0mXJSjp3EgOXKsrlQFQZevc+Qs+2JdXgIElnOT/aDqtRtriDmLlSbtdE8n3ZbA==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -99,6 +150,13 @@ packages: resolution: {integrity: sha512-kNOJ+3vekJJCQKWihNmxBkarJzNW09kP5a9E1SRNiQVNOUEeSwcRR0qYotM65nx821gNzjjhJXnAZ8OazWldrg==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular/animations@19.2.14': + resolution: {integrity: sha512-xhl8fLto5HHJdVj8Nb6EoBEiTAcXuWDYn1q5uHcGxyVH3kiwENWy/2OQXgCr2CuWo2e6hNUGzSLf/cjbsMNqEA==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} + peerDependencies: + '@angular/common': 19.2.14 + '@angular/core': 19.2.14 + '@angular/build@19.2.15': resolution: {integrity: sha512-iE4fp4d5ALu702uoL6/YkjM2JlGEXZ5G+RVzq3W2jg/Ft6ISAQnRKB6mymtetDD6oD7i87e8uSu9kFVNBauX2w==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -215,6 +273,10 @@ packages: resolution: {integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==} engines: {node: '>=6.9.0'} + '@babel/generator@7.26.10': + resolution: {integrity: sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.27.5': resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} engines: {node: '>=6.9.0'} @@ -223,10 +285,35 @@ packages: resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.27.2': resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} + '@babel/helper-create-class-features-plugin@7.27.1': + resolution: {integrity: sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.27.1': + resolution: {integrity: sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.4': + resolution: {integrity: sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-member-expression-to-functions@7.27.1': + resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.27.1': resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} engines: {node: '>=6.9.0'} @@ -237,10 +324,30 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + '@babel/helper-plugin-utils@7.27.1': resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} engines: {node: '>=6.9.0'} + '@babel/helper-remap-async-to-generator@7.27.1': + resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.27.1': + resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + '@babel/helper-split-export-declaration@7.24.7': resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} engines: {node: '>=6.9.0'} @@ -257,6 +364,10 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} + '@babel/helper-wrap-function@7.27.1': + resolution: {integrity: sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==} + engines: {node: '>=6.9.0'} + '@babel/helpers@7.27.6': resolution: {integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==} engines: {node: '>=6.9.0'} @@ -266,12 +377,381 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1': + resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1': + resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1': + resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': + resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.27.1': + resolution: {integrity: sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.27.1': + resolution: {integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-attributes@7.26.0': resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.27.1': + resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.26.8': + resolution: {integrity: sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.25.9': + resolution: {integrity: sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.27.1': + resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.27.5': + resolution: {integrity: sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.27.1': + resolution: {integrity: sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.27.1': + resolution: {integrity: sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.27.1': + resolution: {integrity: sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.27.1': + resolution: {integrity: sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.27.3': + resolution: {integrity: sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.27.1': + resolution: {integrity: sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.27.1': + resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1': + resolution: {integrity: sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.27.1': + resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.27.1': + resolution: {integrity: sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.27.1': + resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.27.1': + resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.27.1': + resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.27.1': + resolution: {integrity: sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.27.1': + resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.27.1': + resolution: {integrity: sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.27.1': + resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.27.1': + resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.27.1': + resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.27.1': + resolution: {integrity: sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.27.1': + resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1': + resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.27.1': + resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.27.1': + resolution: {integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.27.1': + resolution: {integrity: sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.27.3': + resolution: {integrity: sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.27.1': + resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.27.1': + resolution: {integrity: sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.27.1': + resolution: {integrity: sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.27.1': + resolution: {integrity: sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.27.1': + resolution: {integrity: sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.27.1': + resolution: {integrity: sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.27.1': + resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.27.5': + resolution: {integrity: sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.27.1': + resolution: {integrity: sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.27.1': + resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-runtime@7.26.10': + resolution: {integrity: sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.27.1': + resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.27.1': + resolution: {integrity: sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.27.1': + resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.27.1': + resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.27.1': + resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.27.1': + resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.27.1': + resolution: {integrity: sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.27.1': + resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.27.1': + resolution: {integrity: sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.26.9': + resolution: {integrity: sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/runtime@7.26.10': + resolution: {integrity: sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -288,6 +768,10 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@discoveryjs/json-ext@0.6.3': + resolution: {integrity: sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==} + engines: {node: '>=14.17.0'} + '@esbuild/aix-ppc64@0.25.4': resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} engines: {node: '>=18'} @@ -596,12 +1080,36 @@ packages: resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jsonjoy.com/base64@1.1.2': + resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/json-pack@1.2.0': + resolution: {integrity: sha512-io1zEbbYcElht3tdlqEOFxZ0dMTYrHz9iMf0gqn1pPjZFTCgM5R4R5IMA20Chb2UPYYsxjzs8CgZ7Nb5n2K2rA==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/util@1.6.0': + resolution: {integrity: sha512-sw/RMbehRhN68WRtcKCpQOPfnH6lLP4GJfqzi3iYej8tnzpZUDr6UkZYJjcjjC0FWEJOJbyM3PTIwxucUmDG2A==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@leichtgewicht/ip-codec@2.0.5': + resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} + '@listr2/prompt-adapter-inquirer@2.0.18': resolution: {integrity: sha512-0hz44rAcrphyXcA8IS7EJ2SCoaBZD2u5goE8S/e+q/DL+dOGpqpcLidVOFeLG3VgML62SXmfRLAhWt0zL1oW4Q==} engines: {node: '>=18.0.0'} @@ -768,6 +1276,14 @@ packages: resolution: {integrity: sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==} engines: {node: '>= 10'} + '@ngtools/webpack@19.2.15': + resolution: {integrity: sha512-H37nop/wWMkSgoU2VvrMzanHePdLRRrX52nC5tT2ZhH3qP25+PrnMyw11PoLDLv3iWXC68uB1AiKNIT+jiQbuQ==} + engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + '@angular/compiler-cli': ^19.0.0 || ^19.2.0-next.0 + typescript: '>=5.5 <5.9' + webpack: ^5.54.0 + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1026,6 +1542,10 @@ packages: resolution: {integrity: sha512-hVJD77oT67aowHxwT4+M6PGOp+E2LtLdTK3+FC0lBO9T7sYwItDMXZ7Z07IDCvR1M717a4axbIWckrW67KMP/w==} engines: {node: ^18.17.0 || >=20.5.0} + '@sindresorhus/merge-streams@2.3.0': + resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} + engines: {node: '>=18'} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -1037,24 +1557,138 @@ packages: resolution: {integrity: sha512-UUYHISyhCU3ZgN8yaear3cGATHb3SMuKHsQ/nVbHXcmnBf+LzQ/cQfhNG+rfaSHgqGKNEm2cOCLVLELStUQ1JA==} engines: {node: ^18.17.0 || >=20.5.0} + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/bonjour@3.5.13': + resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==} + + '@types/connect-history-api-fallback@1.5.4': + resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/express-serve-static-core@4.19.6': + resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + + '@types/express@4.17.23': + resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/http-proxy@1.17.16': + resolution: {integrity: sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==} + '@types/jasmine@5.1.8': resolution: {integrity: sha512-u7/CnvRdh6AaaIzYjCgUuVbREFgulhX05Qtf6ZtW+aOcjCKKVvKgpkPYJBFTZSHtFBYimzU4zP0V2vrEsq9Wcg==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/node-forge@1.3.11': + resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} + '@types/node@24.0.3': resolution: {integrity: sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==} + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/retry@0.12.2': + resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} + + '@types/send@0.17.5': + resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + + '@types/serve-index@1.9.4': + resolution: {integrity: sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==} + + '@types/serve-static@1.15.8': + resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + + '@types/sockjs@0.3.36': + resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@vitejs/plugin-basic-ssl@1.2.0': resolution: {integrity: sha512-mkQnxTkcldAzIsomk1UuLfAu9n+kpQ3JbHcpCp7d2Oo6ITtji8pHS3QToOWjhPFvNQSnhlkAjmGbhv2QvwO/7Q==} engines: {node: '>=14.21.3'} peerDependencies: vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + '@yarnpkg/lockfile@1.1.0': resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==} @@ -1066,10 +1700,27 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + adjust-sourcemap-loader@4.0.0: + resolution: {integrity: sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==} + engines: {node: '>=8.9'} + agent-base@7.1.3: resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -1078,9 +1729,18 @@ packages: ajv: optional: true + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -1089,6 +1749,11 @@ packages: resolution: {integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==} engines: {node: '>=18'} + ansi-html-community@0.0.8: + resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==} + engines: {'0': node >= 0.8.0} + hasBin: true + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1109,6 +1774,41 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + babel-loader@9.2.1: + resolution: {integrity: sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==} + engines: {node: '>= 14.15.0'} + peerDependencies: + '@babel/core': ^7.12.0 + webpack: '>=5' + + babel-plugin-polyfill-corejs2@0.4.13: + resolution: {integrity: sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.11.1: + resolution: {integrity: sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.4: + resolution: {integrity: sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1119,10 +1819,16 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} + batch@0.6.1: + resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} + beasties@0.3.2: resolution: {integrity: sha512-p4AF8uYzm9Fwu8m/hSVTCPXrRBPmB34hQpHsec2KOaR9CZmgoU8IOv4Cvwq4hgz2p4hLMNbsdNl5XeA6XbAQwA==} engines: {node: '>=14.0.0'} + big.js@5.2.2: + resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -1134,6 +1840,9 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + bonjour-service@1.3.0: + resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -1158,6 +1867,10 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -1174,6 +1887,10 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + caniuse-lite@1.0.30001723: resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==} @@ -1200,6 +1917,10 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -1227,6 +1948,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + clone-deep@4.0.1: + resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} + engines: {node: '>=6'} + clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -1241,13 +1966,35 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + common-path-prefix@3.0.0: + resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} + + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.8.0: + resolution: {integrity: sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==} + engines: {node: '>= 0.8.0'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + connect-history-api-fallback@2.0.0: + resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} + engines: {node: '>=0.8'} + connect@3.7.0: resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} engines: {node: '>= 0.10.0'} + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -1258,18 +2005,61 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + copy-anything@2.0.6: + resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==} + + copy-webpack-plugin@12.0.2: + resolution: {integrity: sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==} + engines: {node: '>= 18.12.0'} + peerDependencies: + webpack: ^5.1.0 + + core-js-compat@3.43.0: + resolution: {integrity: sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-loader@7.1.2: + resolution: {integrity: sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==} + engines: {node: '>= 18.12.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + webpack: ^5.27.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + css-select@5.1.0: resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} @@ -1277,6 +2067,11 @@ packages: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + custom-event@1.0.1: resolution: {integrity: sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==} @@ -1310,9 +2105,25 @@ packages: supports-color: optional: true + default-browser-id@5.0.0: + resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} + engines: {node: '>=18'} + + default-browser@5.2.1: + resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} + engines: {node: '>=18'} + defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1330,9 +2141,16 @@ packages: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + di@0.0.1: resolution: {integrity: sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==} + dns-packet@5.6.1: + resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==} + engines: {node: '>=6'} + dom-serialize@2.2.1: resolution: {integrity: sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==} @@ -1371,10 +2189,18 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + emojis-list@3.0.0: + resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} + engines: {node: '>= 4'} + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} @@ -1386,6 +2212,10 @@ packages: resolution: {integrity: sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==} engines: {node: '>=10.2.0'} + enhanced-resolve@5.18.1: + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + engines: {node: '>=10.13.0'} + ent@2.2.2: resolution: {integrity: sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw==} engines: {node: '>= 0.4'} @@ -1409,6 +2239,13 @@ packages: err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} + hasBin: true + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1417,10 +2254,18 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + esbuild-wasm@0.25.4: + resolution: {integrity: sha512-2HlCS6rNvKWaSKhWaG/YIyRsTsL3gUrMP2ToZMBIjw9LM7vVcIs+rz8kE2vExvTJgvM8OKPqNpcHawY/BQc/qQ==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.25.4: resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==} engines: {node: '>=18'} @@ -1433,15 +2278,47 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + exponential-backoff@3.1.2: resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==} + express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} + engines: {node: '>= 0.10.0'} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -1462,6 +2339,10 @@ packages: fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + faye-websocket@0.11.4: + resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} + engines: {node: '>=0.8.0'} + fdir@6.4.6: resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: @@ -1478,6 +2359,22 @@ packages: resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} engines: {node: '>= 0.8'} + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} + + find-cache-dir@4.0.0: + resolution: {integrity: sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==} + engines: {node: '>=14.16'} + + find-up@6.3.0: + resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} @@ -1494,6 +2391,17 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + fs-extra@8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} @@ -1541,6 +2449,10 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} @@ -1556,6 +2468,10 @@ packages: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} + globby@14.1.0: + resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} + engines: {node: '>=18'} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1563,6 +2479,9 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + handle-thing@2.0.1: + resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1583,6 +2502,9 @@ packages: resolution: {integrity: sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==} engines: {node: ^18.17.0 || >=20.5.0} + hpack.js@2.1.6: + resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -1592,14 +2514,37 @@ packages: http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-deceiver@1.2.7: + resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} + + http-errors@1.6.3: + resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} + engines: {node: '>= 0.6'} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-parser-js@0.5.10: + resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + http-proxy-middleware@2.0.9: + resolution: {integrity: sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/express': ^4.17.13 + peerDependenciesMeta: + '@types/express': + optional: true + + http-proxy-middleware@3.0.5: + resolution: {integrity: sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + http-proxy@1.18.1: resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} engines: {node: '>=8.0.0'} @@ -1608,6 +2553,10 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + hyperdyperid@1.2.0: + resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} + engines: {node: '>=10.18'} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -1616,6 +2565,12 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + icss-utils@5.1.0: + resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -1623,9 +2578,22 @@ packages: resolution: {integrity: sha512-T4gbf83A4NH95zvhVYZc+qWocBBGlpzUXLPGurJggw/WIOwicfXJChLDP/iBZnN5WqROSu5Bm3hhle4z8a8YGQ==} engines: {node: ^18.17.0 || >=20.5.0} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + image-size@0.5.5: + resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==} + engines: {node: '>=0.10.0'} + hasBin: true + immutable@5.1.3: resolution: {integrity: sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -1634,6 +2602,9 @@ packages: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + inherits@2.0.3: + resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1645,6 +2616,17 @@ packages: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + ipaddr.js@2.2.0: + resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} + engines: {node: '>= 10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -1653,6 +2635,11 @@ packages: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1673,14 +2660,35 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-interactive@1.0.0: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} + is-network-error@1.1.0: + resolution: {integrity: sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==} + engines: {node: '>=16'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-plain-obj@3.0.0: + resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} + engines: {node: '>=10'} + + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -1689,6 +2697,16 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} + is-what@3.14.1: + resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==} + + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isbinaryfile@4.0.10: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} engines: {node: '>= 8.0.0'} @@ -1700,6 +2718,10 @@ packages: resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} engines: {node: '>=16'} + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -1733,17 +2755,37 @@ packages: jasmine-core@5.7.1: resolution: {integrity: sha512-QnurrtpKsPoixxG2R3d1xP0St/2kcX5oTZyDyQJMY+Vzi/HUlu1kGm+2V8Tz+9lV991leB1l0xcsyz40s9xOOw==} + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + jsbn@1.1.0: resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-parse-even-better-errors@4.0.0: resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -1786,6 +2828,9 @@ packages: peerDependencies: karma: ^6.0.0 + karma-source-map-support@1.4.0: + resolution: {integrity: sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==} + karma@6.4.4: resolution: {integrity: sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w==} engines: {node: '>= 10'} @@ -1802,6 +2847,42 @@ packages: keycloak-js@26.2.0: resolution: {integrity: sha512-CrFcXTN+d6J0V/1v3Zpioys6qHNWE6yUzVVIsCUAmFn9H14GZ0vuYod+lt+SSpMgWGPuneDZBSGBAeLBFuqjsw==} + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + launch-editor@2.10.0: + resolution: {integrity: sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==} + + less-loader@12.2.0: + resolution: {integrity: sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==} + engines: {node: '>= 18.12.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + less: ^3.5.0 || ^4.0.0 + webpack: ^5.0.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + + less@4.2.2: + resolution: {integrity: sha512-tkuLHQlvWUTeQ3doAqnHbNn8T6WX1KA8yvbKG9x4VtKtIjHsVKQZCH11zRgAfbDAXC2UNIg/K9BYAAcEzUIrNg==} + engines: {node: '>=6'} + hasBin: true + + license-webpack-plugin@4.0.2: + resolution: {integrity: sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==} + peerDependencies: + webpack: '*' + peerDependenciesMeta: + webpack: + optional: true + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + listr2@8.2.5: resolution: {integrity: sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==} engines: {node: '>=18.0.0'} @@ -1810,6 +2891,25 @@ packages: resolution: {integrity: sha512-SuHqzPl7mYStna8WRotY8XX/EUZBjjv3QyKIByeCLFfC9uXT/OIHByEcA07PzbMfQAM0KYJtLgtpMRlIe5dErQ==} hasBin: true + loader-runner@4.3.0: + resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + engines: {node: '>=6.11.5'} + + loader-utils@2.0.4: + resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} + engines: {node: '>=8.9.0'} + + loader-utils@3.3.1: + resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} + engines: {node: '>= 12.13.0'} + + locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -1834,6 +2934,10 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -1850,10 +2954,24 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + memfs@4.17.2: + resolution: {integrity: sha512-NgYhCOWgovOXSzvYgUW0LQ7Qy72rWQMGGFJDoWg4G30RHd3z77VbYdtJ4fembJXBy8pMIUA31XNAupobOQlwdg==} + engines: {node: '>= 4.0.0'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -1866,6 +2984,11 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + mime@2.6.0: resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} engines: {node: '>=4.0.0'} @@ -1879,6 +3002,15 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + mini-css-extract-plugin@2.9.2: + resolution: {integrity: sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^5.0.0 + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1960,6 +3092,10 @@ packages: msgpackr@1.11.4: resolution: {integrity: sha512-uaff7RG9VIC4jacFW9xzL3jc0iM32DNHe4jYVycBcjUePT/Klnfj7pqtWJt9khvDFizmjN2TlYniYmSS2LIaZg==} + multicast-dns@7.2.5: + resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} + hasBin: true + mute-stream@1.0.0: resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -1973,20 +3109,36 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + needle@3.3.1: + resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==} + engines: {node: '>= 4.4.x'} + hasBin: true + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + negotiator@1.0.0: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + node-addon-api@6.1.0: resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-forge@1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} + engines: {node: '>= 6.13.0'} + node-gyp-build-optional-packages@5.2.2: resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} hasBin: true @@ -2008,6 +3160,10 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + npm-bundled@4.0.0: resolution: {integrity: sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==} engines: {node: ^18.17.0 || >=20.5.0} @@ -2047,6 +3203,9 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -2055,6 +3214,10 @@ packages: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} + on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -2066,6 +3229,10 @@ packages: resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} engines: {node: '>=18'} + open@10.1.0: + resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==} + engines: {node: '>=18'} + ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} @@ -2077,10 +3244,22 @@ packages: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + p-map@7.0.3: resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==} engines: {node: '>=18'} + p-retry@6.2.1: + resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==} + engines: {node: '>=16.17'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -2089,6 +3268,18 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} hasBin: true + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-node-version@1.0.1: + resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} + engines: {node: '>= 0.10'} + parse5-html-rewriting-stream@7.0.0: resolution: {integrity: sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==} @@ -2102,6 +3293,10 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -2117,6 +3312,13 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + + path-type@6.0.0: + resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} + engines: {node: '>=18'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2128,12 +3330,68 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + piscina@4.8.0: resolution: {integrity: sha512-EZJb+ZxDrQf3dihsUL7p42pjNyrNIFJCrRHPMgxu/svsj+P3xS3fuEWp7k2+rfsavfl1N0G29b1HGs7J0m8rZA==} + pkg-dir@7.0.0: + resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} + engines: {node: '>=14.16'} + + postcss-loader@8.1.1: + resolution: {integrity: sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==} + engines: {node: '>= 18.12.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + postcss: ^7.0.0 || ^8.0.1 + webpack: ^5.0.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + postcss-media-query-parser@0.2.3: resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} + postcss-modules-extract-imports@3.1.0: + resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-local-by-default@4.2.0: + resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-scope@3.2.1: + resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-values@4.0.0: + resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.2: + resolution: {integrity: sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -2142,10 +3400,20 @@ packages: resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} engines: {node: ^18.17.0 || >=20.5.0} + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + promise-retry@2.0.1: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + punycode@1.4.1: resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} @@ -2160,6 +3428,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -2168,6 +3439,9 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -2183,6 +3457,30 @@ packages: reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + regenerate-unicode-properties@10.2.0: + resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regex-parser@2.3.1: + resolution: {integrity: sha512-yXLRqatcCuKtVHsWrNg0JL3l1zGfdXeEvDa0bdu4tCDQw0RpMDZsqbkyRTUnKMR0tXF627V2oEWjBEaEdqTwtQ==} + + regexpu-core@6.2.0: + resolution: {integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.12.0: + resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} + hasBin: true + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -2194,6 +3492,14 @@ packages: requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-url-loader@5.0.0: + resolution: {integrity: sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==} + engines: {node: '>=12'} + resolve@1.22.10: resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} engines: {node: '>= 0.4'} @@ -2211,6 +3517,10 @@ packages: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -2228,6 +3538,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + run-applescript@7.0.0: + resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} + engines: {node: '>=18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -2241,6 +3555,9 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -2251,11 +3568,50 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sass-loader@16.0.5: + resolution: {integrity: sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==} + engines: {node: '>= 18.12.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + node-sass: ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + sass: ^1.3.0 + sass-embedded: '*' + webpack: ^5.0.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + node-sass: + optional: true + sass: + optional: true + sass-embedded: + optional: true + webpack: + optional: true + sass@1.85.0: resolution: {integrity: sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==} engines: {node: '>=14.0.0'} hasBin: true + sax@1.4.1: + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + + schema-utils@4.3.2: + resolution: {integrity: sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==} + engines: {node: '>= 10.13.0'} + + select-hose@2.0.0: + resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} + + selfsigned@2.4.1: + resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} + engines: {node: '>=10'} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2270,9 +3626,31 @@ packages: engines: {node: '>=10'} hasBin: true + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + serve-index@1.9.1: + resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + + setprototypeof@1.1.0: + resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shallow-clone@3.0.1: + resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} + engines: {node: '>=8'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2281,6 +3659,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -2308,6 +3690,10 @@ packages: resolution: {integrity: sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==} engines: {node: ^18.17.0 || >=20.5.0} + slash@5.1.0: + resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} + engines: {node: '>=14.16'} + slice-ansi@5.0.0: resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} engines: {node: '>=12'} @@ -2331,6 +3717,9 @@ packages: resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} engines: {node: '>=10.2.0'} + sockjs@0.3.24: + resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} + socks-proxy-agent@8.0.5: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} @@ -2343,6 +3732,12 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map-loader@5.0.0: + resolution: {integrity: sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==} + engines: {node: '>= 18.12.0'} + peerDependencies: + webpack: ^5.72.1 + source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -2366,6 +3761,13 @@ packages: spdx-license-ids@3.0.21: resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} + spdy-transport@3.0.0: + resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} + + spdy@4.0.2: + resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} + engines: {node: '>=6.0.0'} + sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} @@ -2397,6 +3799,9 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -2412,6 +3817,10 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -2420,6 +3829,10 @@ packages: resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} engines: {node: '>=0.10'} + tapable@2.2.2: + resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + engines: {node: '>=6'} + tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} @@ -2428,6 +3841,36 @@ packages: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} + terser-webpack-plugin@5.3.14: + resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + + terser@5.39.0: + resolution: {integrity: sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==} + engines: {node: '>=10'} + hasBin: true + + thingies@1.21.0: + resolution: {integrity: sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==} + engines: {node: '>=10.18'} + peerDependencies: + tslib: ^2 + + thunky@1.1.0: + resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} + tinyglobby@0.2.14: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} @@ -2448,6 +3891,16 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + tree-dump@1.0.3: + resolution: {integrity: sha512-il+Cv80yVHFBwokQSfd4bldvr1Md951DpgAGfmhydt04L+YzHgubm2tQ7zueWDcGENKHq0ZvGFR/hjvNXilHEg==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -2463,6 +3916,9 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} + typed-assert@1.0.9: + resolution: {integrity: sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==} + typescript@5.8.3: resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} @@ -2475,6 +3931,26 @@ packages: undici-types@7.8.0: resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.0: + resolution: {integrity: sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + unique-filename@4.0.0: resolution: {integrity: sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==} engines: {node: ^18.17.0 || >=20.5.0} @@ -2504,6 +3980,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -2563,12 +4043,73 @@ packages: resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} engines: {node: '>=10.13.0'} + wbuf@1.7.3: + resolution: {integrity: sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==} + wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} weak-lru-cache@1.2.2: resolution: {integrity: sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==} + webpack-dev-middleware@7.4.2: + resolution: {integrity: sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA==} + engines: {node: '>= 18.12.0'} + peerDependencies: + webpack: ^5.0.0 + peerDependenciesMeta: + webpack: + optional: true + + webpack-dev-server@5.2.2: + resolution: {integrity: sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==} + engines: {node: '>= 18.12.0'} + hasBin: true + peerDependencies: + webpack: ^5.0.0 + webpack-cli: '*' + peerDependenciesMeta: + webpack: + optional: true + webpack-cli: + optional: true + + webpack-merge@6.0.1: + resolution: {integrity: sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==} + engines: {node: '>=18.0.0'} + + webpack-sources@3.3.2: + resolution: {integrity: sha512-ykKKus8lqlgXX/1WjudpIEjqsafjOTcOJqxnAbMLAu/KCsDCJ6GBtvscewvTkrn24HsnvFwrSCbenFrhtcCsAA==} + engines: {node: '>=10.13.0'} + + webpack-subresource-integrity@5.1.0: + resolution: {integrity: sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==} + engines: {node: '>= 12'} + peerDependencies: + html-webpack-plugin: '>= 5.0.0-beta.1 < 6' + webpack: ^5.12.0 + peerDependenciesMeta: + html-webpack-plugin: + optional: true + + webpack@5.98.0: + resolution: {integrity: sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + websocket-driver@0.7.4: + resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} + engines: {node: '>=0.8.0'} + + websocket-extensions@0.1.4: + resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} + engines: {node: '>=0.8.0'} + which@1.3.1: resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} hasBin: true @@ -2583,6 +4124,9 @@ packages: engines: {node: ^18.17.0 || >=20.5.0} hasBin: true + wildcard@2.0.1: + resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -2614,6 +4158,18 @@ packages: utf-8-validate: optional: true + ws@8.18.2: + resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -2644,6 +4200,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yocto-queue@1.2.1: + resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} + engines: {node: '>=12.20'} + yoctocolors-cjs@2.1.2: resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} engines: {node: '>=18'} @@ -2665,6 +4225,100 @@ snapshots: transitivePeerDependencies: - chokidar + '@angular-devkit/build-angular@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(typescript@5.8.3)(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@angular-devkit/architect': 0.1902.15(chokidar@4.0.3) + '@angular-devkit/build-webpack': 0.1902.15(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.98.0))(webpack@5.98.0(esbuild@0.25.4)) + '@angular-devkit/core': 19.2.15(chokidar@4.0.3) + '@angular/build': 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(terser@5.39.0)(typescript@5.8.3) + '@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3) + '@babel/core': 7.26.10 + '@babel/generator': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/plugin-transform-async-generator-functions': 7.26.8(@babel/core@7.26.10) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-runtime': 7.26.10(@babel/core@7.26.10) + '@babel/preset-env': 7.26.9(@babel/core@7.26.10) + '@babel/runtime': 7.26.10 + '@discoveryjs/json-ext': 0.6.3 + '@ngtools/webpack': 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.4)) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)) + ansi-colors: 4.1.3 + autoprefixer: 10.4.20(postcss@8.5.2) + babel-loader: 9.2.1(@babel/core@7.26.10)(webpack@5.98.0(esbuild@0.25.4)) + browserslist: 4.25.0 + copy-webpack-plugin: 12.0.2(webpack@5.98.0(esbuild@0.25.4)) + css-loader: 7.1.2(webpack@5.98.0(esbuild@0.25.4)) + esbuild-wasm: 0.25.4 + fast-glob: 3.3.3 + http-proxy-middleware: 3.0.5 + istanbul-lib-instrument: 6.0.3 + jsonc-parser: 3.3.1 + karma-source-map-support: 1.4.0 + less: 4.2.2 + less-loader: 12.2.0(less@4.2.2)(webpack@5.98.0(esbuild@0.25.4)) + license-webpack-plugin: 4.0.2(webpack@5.98.0(esbuild@0.25.4)) + loader-utils: 3.3.1 + mini-css-extract-plugin: 2.9.2(webpack@5.98.0(esbuild@0.25.4)) + open: 10.1.0 + ora: 5.4.1 + picomatch: 4.0.2 + piscina: 4.8.0 + postcss: 8.5.2 + postcss-loader: 8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.4)) + resolve-url-loader: 5.0.0 + rxjs: 7.8.1 + sass: 1.85.0 + sass-loader: 16.0.5(sass@1.85.0)(webpack@5.98.0(esbuild@0.25.4)) + semver: 7.7.1 + source-map-loader: 5.0.0(webpack@5.98.0(esbuild@0.25.4)) + source-map-support: 0.5.21 + terser: 5.39.0 + tree-kill: 1.2.2 + tslib: 2.8.1 + typescript: 5.8.3 + webpack: 5.98.0(esbuild@0.25.4) + webpack-dev-middleware: 7.4.2(webpack@5.98.0) + webpack-dev-server: 5.2.2(webpack@5.98.0) + webpack-merge: 6.0.1 + webpack-subresource-integrity: 5.1.0(webpack@5.98.0(esbuild@0.25.4)) + optionalDependencies: + esbuild: 0.25.4 + karma: 6.4.4 + transitivePeerDependencies: + - '@angular/compiler' + - '@rspack/core' + - '@swc/core' + - '@types/node' + - bufferutil + - chokidar + - debug + - html-webpack-plugin + - jiti + - lightningcss + - node-sass + - sass-embedded + - stylus + - sugarss + - supports-color + - tsx + - uglify-js + - utf-8-validate + - vite + - webpack-cli + - yaml + + '@angular-devkit/build-webpack@0.1902.15(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.98.0))(webpack@5.98.0(esbuild@0.25.4))': + dependencies: + '@angular-devkit/architect': 0.1902.15(chokidar@4.0.3) + rxjs: 7.8.1 + webpack: 5.98.0(esbuild@0.25.4) + webpack-dev-server: 5.2.2(webpack@5.98.0) + transitivePeerDependencies: + - chokidar + '@angular-devkit/core@19.2.15(chokidar@4.0.3)': dependencies: ajv: 8.17.1 @@ -2686,7 +4340,14 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular/build@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(karma@6.4.4)(postcss@8.5.6)(typescript@5.8.3)': + '@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))': + dependencies: + '@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) + tslib: 2.8.1 + optional: true + + '@angular/build@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(terser@5.39.0)(typescript@5.8.3)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.15(chokidar@4.0.3) @@ -2697,7 +4358,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.10) '@inquirer/confirm': 5.1.6(@types/node@24.0.3) - '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.2.7(@types/node@24.0.3)(sass@1.85.0)) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)) beasties: 0.3.2 browserslist: 4.25.0 esbuild: 0.25.4 @@ -2715,12 +4376,13 @@ snapshots: semver: 7.7.1 source-map-support: 0.5.21 typescript: 5.8.3 - vite: 6.2.7(@types/node@24.0.3)(sass@1.85.0) + vite: 6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0) watchpack: 2.4.2 optionalDependencies: karma: 6.4.4 + less: 4.2.2 lmdb: 3.2.6 - postcss: 8.5.6 + postcss: 8.5.2 transitivePeerDependencies: - '@types/node' - chokidar @@ -2789,25 +4451,27 @@ snapshots: tslib: 2.8.1 zone.js: 0.15.1 - '@angular/forms@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': + '@angular/forms@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': dependencies: '@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) - '@angular/platform-browser': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)) + '@angular/platform-browser': 19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))': + '@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))': dependencies: '@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) tslib: 2.8.1 + optionalDependencies: + '@angular/animations': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)) - '@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': + '@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2)': dependencies: '@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) - '@angular/platform-browser': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)) + '@angular/platform-browser': 19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)) rxjs: 7.8.2 tslib: 2.8.1 @@ -2879,6 +4543,14 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/generator@7.26.10': + dependencies: + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + '@babel/generator@7.27.5': dependencies: '@babel/parser': 7.27.5 @@ -2891,6 +4563,10 @@ snapshots: dependencies: '@babel/types': 7.27.6 + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.27.6 + '@babel/helper-compilation-targets@7.27.2': dependencies: '@babel/compat-data': 7.27.5 @@ -2899,6 +4575,44 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 + '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.26.10) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.27.4 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.27.3 + regexpu-core: 6.2.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-plugin-utils': 7.27.1 + debug: 4.4.1 + lodash.debounce: 4.0.8 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-member-expression-to-functions@7.27.1': + dependencies: + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + transitivePeerDependencies: + - supports-color + '@babel/helper-module-imports@7.27.1': dependencies: '@babel/traverse': 7.27.4 @@ -2933,8 +4647,37 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.27.6 + '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-wrap-function': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + transitivePeerDependencies: + - supports-color + '@babel/helper-split-export-declaration@7.24.7': dependencies: '@babel/types': 7.27.6 @@ -2945,6 +4688,14 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} + '@babel/helper-wrap-function@7.27.1': + dependencies: + '@babel/template': 7.27.2 + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + transitivePeerDependencies: + - supports-color + '@babel/helpers@7.27.6': dependencies: '@babel/template': 7.27.2 @@ -2954,11 +4705,478 @@ snapshots: dependencies: '@babel/types': 7.27.6 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-async-generator-functions@7.26.8(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.26.10) + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-block-scoping@7.27.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.26.10) + '@babel/traverse': 7.27.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/template': 7.27.2 + + '@babel/plugin-transform-destructuring@7.27.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-exponentiation-operator@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-logical-assignment-operators@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-object-rest-spread@7.27.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-destructuring': 7.27.3(@babel/core@7.26.10) + '@babel/plugin-transform-parameters': 7.27.1(@babel/core@7.26.10) + + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-optional-chaining@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-regenerator@7.27.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-runtime@7.26.10(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.26.10) + babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.26.10) + babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.26.10) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-spread@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/preset-env@7.26.9(@babel/core@7.26.10)': + dependencies: + '@babel/compat-data': 7.27.5 + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.10) + '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.10) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-async-generator-functions': 7.26.8(@babel/core@7.26.10) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-block-scoping': 7.27.5(@babel/core@7.26.10) + '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-class-static-block': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-classes': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-destructuring': 7.27.3(@babel/core@7.26.10) + '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-exponentiation-operator': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-logical-assignment-operators': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-modules-systemjs': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-object-rest-spread': 7.27.3(@babel/core@7.26.10) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-parameters': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-regenerator': 7.27.5(@babel/core@7.26.10) + '@babel/plugin-transform-regexp-modifiers': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-property-regex': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-sets-regex': 7.27.1(@babel/core@7.26.10) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.10) + babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.26.10) + babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.26.10) + babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.26.10) + core-js-compat: 3.43.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/types': 7.27.6 + esutils: 2.0.3 + + '@babel/runtime@7.26.10': + dependencies: + regenerator-runtime: 0.14.1 + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -2984,6 +5202,8 @@ snapshots: '@colors/colors@1.5.0': {} + '@discoveryjs/json-ext@0.6.3': {} + '@esbuild/aix-ppc64@0.25.4': optional: true @@ -3211,6 +5431,11 @@ snapshots: '@jridgewell/set-array@1.2.1': {} + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/sourcemap-codec@1.5.0': {} '@jridgewell/trace-mapping@0.3.25': @@ -3218,6 +5443,24 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + + '@jsonjoy.com/json-pack@1.2.0(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) + '@jsonjoy.com/util': 1.6.0(tslib@2.8.1) + hyperdyperid: 1.2.0 + thingies: 1.21.0(tslib@2.8.1) + tslib: 2.8.1 + + '@jsonjoy.com/util@1.6.0(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + + '@leichtgewicht/ip-codec@2.0.5': {} + '@listr2/prompt-adapter-inquirer@2.0.18(@inquirer/prompts@7.3.2(@types/node@24.0.3))': dependencies: '@inquirer/prompts': 7.3.2(@types/node@24.0.3) @@ -3327,6 +5570,12 @@ snapshots: '@napi-rs/nice-win32-x64-msvc': 1.0.1 optional: true + '@ngtools/webpack@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.4))': + dependencies: + '@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3) + typescript: 5.8.3 + webpack: 5.98.0(esbuild@0.25.4) + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -3559,6 +5808,8 @@ snapshots: '@sigstore/core': 2.0.0 '@sigstore/protobuf-specs': 0.4.3 + '@sindresorhus/merge-streams@2.3.0': {} + '@socket.io/component-emitter@3.1.2': {} '@tufjs/canonical-json@2.0.0': {} @@ -3568,21 +5819,186 @@ snapshots: '@tufjs/canonical-json': 2.0.0 minimatch: 9.0.5 + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 24.0.3 + + '@types/bonjour@3.5.13': + dependencies: + '@types/node': 24.0.3 + + '@types/connect-history-api-fallback@1.5.4': + dependencies: + '@types/express-serve-static-core': 4.19.6 + '@types/node': 24.0.3 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 24.0.3 + '@types/cors@2.8.19': dependencies: '@types/node': 24.0.3 + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.6 + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + '@types/estree@1.0.6': {} + '@types/express-serve-static-core@4.19.6': + dependencies: + '@types/node': 24.0.3 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + + '@types/express@4.17.23': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.6 + '@types/qs': 6.14.0 + '@types/serve-static': 1.15.8 + + '@types/http-errors@2.0.5': {} + + '@types/http-proxy@1.17.16': + dependencies: + '@types/node': 24.0.3 + '@types/jasmine@5.1.8': {} + '@types/json-schema@7.0.15': {} + + '@types/mime@1.3.5': {} + + '@types/node-forge@1.3.11': + dependencies: + '@types/node': 24.0.3 + '@types/node@24.0.3': dependencies: undici-types: 7.8.0 - '@vitejs/plugin-basic-ssl@1.2.0(vite@6.2.7(@types/node@24.0.3)(sass@1.85.0))': + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/retry@0.12.2': {} + + '@types/send@0.17.5': dependencies: - vite: 6.2.7(@types/node@24.0.3)(sass@1.85.0) + '@types/mime': 1.3.5 + '@types/node': 24.0.3 + + '@types/serve-index@1.9.4': + dependencies: + '@types/express': 4.17.23 + + '@types/serve-static@1.15.8': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 24.0.3 + '@types/send': 0.17.5 + + '@types/sockjs@0.3.36': + dependencies: + '@types/node': 24.0.3 + + '@types/ws@8.18.1': + dependencies: + '@types/node': 24.0.3 + + '@vitejs/plugin-basic-ssl@1.2.0(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0))': + dependencies: + vite: 6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0) + + '@webassemblyjs/ast@1.14.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + + '@webassemblyjs/helper-api-error@1.13.2': {} + + '@webassemblyjs/helper-buffer@1.14.1': {} + + '@webassemblyjs/helper-numbers@1.13.2': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + + '@webassemblyjs/helper-wasm-section@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 + + '@webassemblyjs/ieee754@1.13.2': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.13.2': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.13.2': {} + + '@webassemblyjs/wasm-edit@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 + + '@webassemblyjs/wasm-gen@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wasm-opt@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + + '@webassemblyjs/wasm-parser@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wast-printer@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@xtuc/long': 4.2.2 + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} '@yarnpkg/lockfile@1.1.0': {} @@ -3593,12 +6009,28 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + acorn@8.15.0: {} + + adjust-sourcemap-loader@4.0.0: + dependencies: + loader-utils: 2.0.4 + regex-parser: 2.3.1 + agent-base@7.1.3: {} + ajv-formats@2.1.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + ajv-formats@3.0.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 + ajv-keywords@5.1.0(ajv@8.17.1): + dependencies: + ajv: 8.17.1 + fast-deep-equal: 3.1.3 + ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 @@ -3606,6 +6038,8 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ansi-colors@4.1.3: {} + ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 @@ -3614,6 +6048,8 @@ snapshots: dependencies: environment: 1.1.0 + ansi-html-community@0.0.8: {} + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -3629,12 +6065,59 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + argparse@2.0.1: {} + + array-flatten@1.1.1: {} + + autoprefixer@10.4.20(postcss@8.5.2): + dependencies: + browserslist: 4.25.0 + caniuse-lite: 1.0.30001723 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.2 + postcss-value-parser: 4.2.0 + + babel-loader@9.2.1(@babel/core@7.26.10)(webpack@5.98.0(esbuild@0.25.4)): + dependencies: + '@babel/core': 7.26.10 + find-cache-dir: 4.0.0 + schema-utils: 4.3.2 + webpack: 5.98.0(esbuild@0.25.4) + + babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.26.10): + dependencies: + '@babel/compat-data': 7.27.5 + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.26.10) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.26.10) + core-js-compat: 3.43.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.4(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + balanced-match@1.0.2: {} base64-js@1.5.1: {} base64id@2.0.0: {} + batch@0.6.1: {} + beasties@0.3.2: dependencies: css-select: 5.1.0 @@ -3646,6 +6129,8 @@ snapshots: postcss: 8.5.6 postcss-media-query-parser: 0.2.3 + big.js@5.2.2: {} + binary-extensions@2.3.0: {} bl@4.1.0: @@ -3671,6 +6156,11 @@ snapshots: transitivePeerDependencies: - supports-color + bonjour-service@1.3.0: + dependencies: + fast-deep-equal: 3.1.3 + multicast-dns: 7.2.5 + boolbase@1.0.0: {} brace-expansion@1.1.12: @@ -3700,6 +6190,10 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bundle-name@4.1.0: + dependencies: + run-applescript: 7.0.0 + bytes@3.1.2: {} cacache@19.0.1: @@ -3727,6 +6221,8 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + callsites@3.1.0: {} + caniuse-lite@1.0.30001723: {} chalk@4.1.2: @@ -3756,6 +6252,8 @@ snapshots: chownr@3.0.0: {} + chrome-trace-event@1.0.4: {} + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -3785,6 +6283,12 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clone-deep@4.0.1: + dependencies: + is-plain-object: 2.0.4 + kind-of: 6.0.3 + shallow-clone: 3.0.1 + clone@1.0.4: {} color-convert@2.0.1: @@ -3795,8 +6299,30 @@ snapshots: colorette@2.0.20: {} + commander@2.20.3: {} + + common-path-prefix@3.0.0: {} + + compressible@2.0.18: + dependencies: + mime-db: 1.52.0 + + compression@1.8.0: + dependencies: + bytes: 3.1.2 + compressible: 2.0.18 + debug: 2.6.9 + negotiator: 0.6.4 + on-headers: 1.0.2 + safe-buffer: 5.2.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + concat-map@0.0.1: {} + connect-history-api-fallback@2.0.0: {} + connect@3.7.0: dependencies: debug: 2.6.9 @@ -3806,25 +6332,75 @@ snapshots: transitivePeerDependencies: - supports-color + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + content-type@1.0.5: {} convert-source-map@1.9.0: {} convert-source-map@2.0.0: {} + cookie-signature@1.0.6: {} + + cookie@0.7.1: {} + cookie@0.7.2: {} + copy-anything@2.0.6: + dependencies: + is-what: 3.14.1 + + copy-webpack-plugin@12.0.2(webpack@5.98.0(esbuild@0.25.4)): + dependencies: + fast-glob: 3.3.3 + glob-parent: 6.0.2 + globby: 14.1.0 + normalize-path: 3.0.0 + schema-utils: 4.3.2 + serialize-javascript: 6.0.2 + webpack: 5.98.0(esbuild@0.25.4) + + core-js-compat@3.43.0: + dependencies: + browserslist: 4.25.0 + + core-util-is@1.0.3: {} + cors@2.8.5: dependencies: object-assign: 4.1.1 vary: 1.1.2 + cosmiconfig@9.0.0(typescript@5.8.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.8.3 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 + css-loader@7.1.2(webpack@5.98.0(esbuild@0.25.4)): + dependencies: + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.6) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.6) + postcss-modules-scope: 3.2.1(postcss@8.5.6) + postcss-modules-values: 4.0.0(postcss@8.5.6) + postcss-value-parser: 4.2.0 + semver: 7.7.2 + optionalDependencies: + webpack: 5.98.0(esbuild@0.25.4) + css-select@5.1.0: dependencies: boolbase: 1.0.0 @@ -3835,6 +6411,8 @@ snapshots: css-what@6.1.0: {} + cssesc@3.0.0: {} + custom-event@1.0.1: {} date-format@4.0.14: {} @@ -3851,10 +6429,21 @@ snapshots: dependencies: ms: 2.1.3 + default-browser-id@5.0.0: {} + + default-browser@5.2.1: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.0 + defaults@1.0.4: dependencies: clone: 1.0.4 + define-lazy-prop@3.0.0: {} + + depd@1.1.2: {} + depd@2.0.0: {} destroy@1.2.0: {} @@ -3865,8 +6454,14 @@ snapshots: detect-libc@2.0.4: optional: true + detect-node@2.1.0: {} + di@0.0.1: {} + dns-packet@5.6.1: + dependencies: + '@leichtgewicht/ip-codec': 2.0.5 + dom-serialize@2.2.1: dependencies: custom-event: 1.0.1 @@ -3910,8 +6505,12 @@ snapshots: emoji-regex@9.2.2: {} + emojis-list@3.0.0: {} + encodeurl@1.0.2: {} + encodeurl@2.0.0: {} + encoding@0.1.13: dependencies: iconv-lite: 0.6.3 @@ -3935,6 +6534,11 @@ snapshots: - supports-color - utf-8-validate + enhanced-resolve@5.18.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + ent@2.2.2: dependencies: call-bound: 1.0.4 @@ -3952,14 +6556,27 @@ snapshots: err-code@2.0.3: {} + errno@0.1.8: + dependencies: + prr: 1.0.1 + optional: true + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + es-define-property@1.0.1: {} es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 + esbuild-wasm@0.25.4: {} + esbuild@0.25.4: optionalDependencies: '@esbuild/aix-ppc64': 0.25.4 @@ -3992,12 +6609,67 @@ snapshots: escape-html@1.0.3: {} + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + eventemitter3@4.0.7: {} eventemitter3@5.0.1: {} + events@3.3.0: {} + exponential-backoff@3.1.2: {} + express@4.21.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extend@3.0.2: {} external-editor@3.1.0: @@ -4022,6 +6694,10 @@ snapshots: dependencies: reusify: 1.1.0 + faye-websocket@0.11.4: + dependencies: + websocket-driver: 0.7.4 + fdir@6.4.6(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -4042,15 +6718,47 @@ snapshots: transitivePeerDependencies: - supports-color + finalhandler@1.3.1: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-cache-dir@4.0.0: + dependencies: + common-path-prefix: 3.0.0 + pkg-dir: 7.0.0 + + find-up@6.3.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + + flat@5.0.2: {} + flatted@3.3.3: {} - follow-redirects@1.15.9: {} + follow-redirects@1.15.9(debug@4.4.1): + optionalDependencies: + debug: 4.4.1 foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 signal-exit: 4.1.0 + forwarded@0.2.0: {} + + fraction.js@4.3.7: {} + + fresh@0.5.2: {} + fs-extra@8.1.0: dependencies: graceful-fs: 4.2.11 @@ -4100,6 +6808,10 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob-to-regexp@0.4.1: {} glob@10.4.5: @@ -4122,10 +6834,21 @@ snapshots: globals@11.12.0: {} + globby@14.1.0: + dependencies: + '@sindresorhus/merge-streams': 2.3.0 + fast-glob: 3.3.3 + ignore: 7.0.5 + path-type: 6.0.0 + slash: 5.1.0 + unicorn-magic: 0.3.0 + gopd@1.2.0: {} graceful-fs@4.2.11: {} + handle-thing@2.0.1: {} + has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -4142,6 +6865,13 @@ snapshots: dependencies: lru-cache: 10.4.3 + hpack.js@2.1.6: + dependencies: + inherits: 2.0.4 + obuf: 1.1.2 + readable-stream: 2.3.8 + wbuf: 1.7.3 + html-escaper@2.0.2: {} htmlparser2@10.0.0: @@ -4153,6 +6883,15 @@ snapshots: http-cache-semantics@4.2.0: {} + http-deceiver@1.2.7: {} + + http-errors@1.6.3: + dependencies: + depd: 1.1.2 + inherits: 2.0.3 + setprototypeof: 1.1.0 + statuses: 1.5.0 + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -4161,6 +6900,8 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-parser-js@0.5.10: {} + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 @@ -4168,10 +6909,33 @@ snapshots: transitivePeerDependencies: - supports-color - http-proxy@1.18.1: + http-proxy-middleware@2.0.9(@types/express@4.17.23): + dependencies: + '@types/http-proxy': 1.17.16 + http-proxy: 1.18.1(debug@4.4.1) + is-glob: 4.0.3 + is-plain-obj: 3.0.0 + micromatch: 4.0.8 + optionalDependencies: + '@types/express': 4.17.23 + transitivePeerDependencies: + - debug + + http-proxy-middleware@3.0.5: + dependencies: + '@types/http-proxy': 1.17.16 + debug: 4.4.1 + http-proxy: 1.18.1(debug@4.4.1) + is-glob: 4.0.3 + is-plain-object: 5.0.0 + micromatch: 4.0.8 + transitivePeerDependencies: + - supports-color + + http-proxy@1.18.1(debug@4.4.1): dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.9 + follow-redirects: 1.15.9(debug@4.4.1) requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -4183,6 +6947,8 @@ snapshots: transitivePeerDependencies: - supports-color + hyperdyperid@1.2.0: {} + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -4190,7 +6956,10 @@ snapshots: iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 - optional: true + + icss-utils@5.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 ieee754@1.2.1: {} @@ -4198,8 +6967,18 @@ snapshots: dependencies: minimatch: 9.0.5 + ignore@7.0.5: {} + + image-size@0.5.5: + optional: true + immutable@5.1.3: {} + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + imurmurhash@0.1.4: {} inflight@1.0.6: @@ -4207,6 +6986,8 @@ snapshots: once: 1.4.0 wrappy: 1.0.2 + inherits@2.0.3: {} + inherits@2.0.4: {} ini@5.0.0: {} @@ -4216,6 +6997,12 @@ snapshots: jsbn: 1.1.0 sprintf-js: 1.1.3 + ipaddr.js@1.9.1: {} + + ipaddr.js@2.2.0: {} + + is-arrayish@0.2.1: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -4224,6 +7011,8 @@ snapshots: dependencies: hasown: 2.0.2 + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -4238,10 +7027,24 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-interactive@1.0.0: {} + is-network-error@1.1.0: {} + is-number@7.0.0: {} + is-plain-obj@3.0.0: {} + + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-plain-object@5.0.0: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -4251,12 +7054,22 @@ snapshots: is-unicode-supported@0.1.0: {} + is-what@3.14.1: {} + + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + + isarray@1.0.0: {} + isbinaryfile@4.0.10: {} isexe@2.0.0: {} isexe@3.1.1: {} + isobject@3.0.1: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@5.2.1: @@ -4271,11 +7084,11 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.26.10 + '@babel/core': 7.27.4 '@babel/parser': 7.27.5 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.7.1 + semver: 7.7.2 transitivePeerDependencies: - supports-color @@ -4308,12 +7121,28 @@ snapshots: jasmine-core@5.7.1: {} + jest-worker@27.5.1: + dependencies: + '@types/node': 24.0.3 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jiti@1.21.7: {} + js-tokens@4.0.0: {} + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + jsbn@1.1.0: {} + jsesc@3.0.2: {} + jsesc@3.1.0: {} + json-parse-even-better-errors@2.3.1: {} + json-parse-even-better-errors@4.0.0: {} json-schema-traverse@1.0.0: {} @@ -4354,6 +7183,10 @@ snapshots: jasmine-core: 4.6.1 karma: 6.4.4 + karma-source-map-support@1.4.0: + dependencies: + source-map-support: 0.5.21 + karma@6.4.4: dependencies: '@colors/colors': 1.5.0 @@ -4365,7 +7198,7 @@ snapshots: dom-serialize: 2.2.1 glob: 7.2.3 graceful-fs: 4.2.11 - http-proxy: 1.18.1 + http-proxy: 1.18.1(debug@4.4.1) isbinaryfile: 4.0.10 lodash: 4.17.21 log4js: 6.9.1 @@ -4386,16 +7219,51 @@ snapshots: - supports-color - utf-8-validate - keycloak-angular@19.0.2(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(keycloak-js@26.2.0): + keycloak-angular@19.0.2(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(keycloak-js@26.2.0): dependencies: '@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) - '@angular/router': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/router': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) keycloak-js: 26.2.0 tslib: 2.8.1 keycloak-js@26.2.0: {} + kind-of@6.0.3: {} + + launch-editor@2.10.0: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.8.3 + + less-loader@12.2.0(less@4.2.2)(webpack@5.98.0(esbuild@0.25.4)): + dependencies: + less: 4.2.2 + optionalDependencies: + webpack: 5.98.0(esbuild@0.25.4) + + less@4.2.2: + dependencies: + copy-anything: 2.0.6 + parse-node-version: 1.0.1 + tslib: 2.8.1 + optionalDependencies: + errno: 0.1.8 + graceful-fs: 4.2.11 + image-size: 0.5.5 + make-dir: 2.1.0 + mime: 1.6.0 + needle: 3.3.1 + source-map: 0.6.1 + + license-webpack-plugin@4.0.2(webpack@5.98.0(esbuild@0.25.4)): + dependencies: + webpack-sources: 3.3.2 + optionalDependencies: + webpack: 5.98.0(esbuild@0.25.4) + + lines-and-columns@1.2.4: {} + listr2@8.2.5: dependencies: cli-truncate: 4.0.0 @@ -4421,6 +7289,22 @@ snapshots: '@lmdb/lmdb-win32-x64': 3.2.6 optional: true + loader-runner@4.3.0: {} + + loader-utils@2.0.4: + dependencies: + big.js: 5.2.2 + emojis-list: 3.0.0 + json5: 2.2.3 + + loader-utils@3.3.1: {} + + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + + lodash.debounce@4.0.8: {} + lodash@4.17.21: {} log-symbols@4.1.0: @@ -4456,6 +7340,12 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + make-dir@2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.2 + optional: true + make-dir@4.0.0: dependencies: semver: 7.7.2 @@ -4480,8 +7370,21 @@ snapshots: media-typer@0.3.0: {} + memfs@4.17.2: + dependencies: + '@jsonjoy.com/json-pack': 1.2.0(tslib@2.8.1) + '@jsonjoy.com/util': 1.6.0(tslib@2.8.1) + tree-dump: 1.0.3(tslib@2.8.1) + tslib: 2.8.1 + + merge-descriptors@1.0.3: {} + + merge-stream@2.0.0: {} + merge2@1.4.1: {} + methods@1.1.2: {} + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -4493,12 +7396,22 @@ snapshots: dependencies: mime-db: 1.52.0 + mime@1.6.0: {} + mime@2.6.0: {} mimic-fn@2.1.0: {} mimic-function@5.0.1: {} + mini-css-extract-plugin@2.9.2(webpack@5.98.0(esbuild@0.25.4)): + dependencies: + schema-utils: 4.3.2 + tapable: 2.2.2 + webpack: 5.98.0(esbuild@0.25.4) + + minimalistic-assert@1.0.1: {} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -4581,22 +7494,39 @@ snapshots: msgpackr-extract: 3.0.3 optional: true + multicast-dns@7.2.5: + dependencies: + dns-packet: 5.6.1 + thunky: 1.1.0 + mute-stream@1.0.0: {} mute-stream@2.0.0: {} nanoid@3.3.11: {} + needle@3.3.1: + dependencies: + iconv-lite: 0.6.3 + sax: 1.4.1 + optional: true + negotiator@0.6.3: {} + negotiator@0.6.4: {} + negotiator@1.0.0: {} + neo-async@2.6.2: {} + node-addon-api@6.1.0: optional: true node-addon-api@7.1.1: optional: true + node-forge@1.3.1: {} + node-gyp-build-optional-packages@5.2.2: dependencies: detect-libc: 2.0.4 @@ -4625,6 +7555,8 @@ snapshots: normalize-path@3.0.0: {} + normalize-range@0.1.2: {} + npm-bundled@4.0.0: dependencies: npm-normalize-package-bin: 4.0.0 @@ -4674,6 +7606,8 @@ snapshots: object-inspect@1.13.4: {} + obuf@1.1.2: {} + on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -4682,6 +7616,8 @@ snapshots: dependencies: ee-first: 1.1.1 + on-headers@1.0.2: {} + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -4694,6 +7630,13 @@ snapshots: dependencies: mimic-function: 5.0.1 + open@10.1.0: + dependencies: + default-browser: 5.2.1 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + is-wsl: 3.1.0 + ora@5.4.1: dependencies: bl: 4.1.0 @@ -4711,8 +7654,22 @@ snapshots: os-tmpdir@1.0.2: {} + p-limit@4.0.0: + dependencies: + yocto-queue: 1.2.1 + + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + p-map@7.0.3: {} + p-retry@6.2.1: + dependencies: + '@types/retry': 0.12.2 + is-network-error: 1.1.0 + retry: 0.13.1 + package-json-from-dist@1.0.1: {} pacote@20.0.0: @@ -4737,6 +7694,19 @@ snapshots: transitivePeerDependencies: - supports-color + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-node-version@1.0.1: {} + parse5-html-rewriting-stream@7.0.0: dependencies: entities: 4.5.0 @@ -4753,6 +7723,8 @@ snapshots: parseurl@1.3.3: {} + path-exists@5.0.0: {} + path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -4764,18 +7736,74 @@ snapshots: lru-cache: 10.4.3 minipass: 7.1.2 + path-to-regexp@0.1.12: {} + + path-type@6.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} picomatch@4.0.2: {} + pify@4.0.1: + optional: true + piscina@4.8.0: optionalDependencies: '@napi-rs/nice': 1.0.1 + pkg-dir@7.0.0: + dependencies: + find-up: 6.3.0 + + postcss-loader@8.1.1(postcss@8.5.2)(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.4)): + dependencies: + cosmiconfig: 9.0.0(typescript@5.8.3) + jiti: 1.21.7 + postcss: 8.5.2 + semver: 7.7.2 + optionalDependencies: + webpack: 5.98.0(esbuild@0.25.4) + transitivePeerDependencies: + - typescript + postcss-media-query-parser@0.2.3: {} + postcss-modules-extract-imports@3.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-modules-local-by-default@4.2.0(postcss@8.5.6): + dependencies: + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 + postcss-selector-parser: 7.1.0 + postcss-value-parser: 4.2.0 + + postcss-modules-scope@3.2.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 7.1.0 + + postcss-modules-values@4.0.0(postcss@8.5.6): + dependencies: + icss-utils: 5.1.0(postcss@8.5.6) + postcss: 8.5.6 + + postcss-selector-parser@7.1.0: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.2: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -4784,11 +7812,21 @@ snapshots: proc-log@5.0.0: {} + process-nextick-args@2.0.1: {} + promise-retry@2.0.1: dependencies: err-code: 2.0.3 retry: 0.12.0 + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + prr@1.0.1: + optional: true + punycode@1.4.1: {} qjobs@1.2.0: {} @@ -4799,6 +7837,10 @@ snapshots: queue-microtask@1.2.3: {} + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + range-parser@1.2.1: {} raw-body@2.5.2: @@ -4808,6 +7850,16 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -4822,12 +7874,47 @@ snapshots: reflect-metadata@0.2.2: {} + regenerate-unicode-properties@10.2.0: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.14.1: {} + + regex-parser@2.3.1: {} + + regexpu-core@6.2.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.0 + regjsgen: 0.8.0 + regjsparser: 0.12.0 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.0 + + regjsgen@0.8.0: {} + + regjsparser@0.12.0: + dependencies: + jsesc: 3.0.2 + require-directory@2.1.1: {} require-from-string@2.0.2: {} requires-port@1.0.0: {} + resolve-from@4.0.0: {} + + resolve-url-loader@5.0.0: + dependencies: + adjust-sourcemap-loader: 4.0.0 + convert-source-map: 1.9.0 + loader-utils: 2.0.4 + postcss: 8.5.6 + source-map: 0.6.1 + resolve@1.22.10: dependencies: is-core-module: 2.16.1 @@ -4846,6 +7933,8 @@ snapshots: retry@0.12.0: {} + retry@0.13.1: {} + reusify@1.1.0: {} rfdc@1.4.1: {} @@ -4879,6 +7968,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.34.8 fsevents: 2.3.3 + run-applescript@7.0.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -4893,6 +7984,8 @@ snapshots: dependencies: tslib: 2.8.1 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} safe-regex-test@1.1.0: @@ -4903,6 +7996,13 @@ snapshots: safer-buffer@2.1.2: {} + sass-loader@16.0.5(sass@1.85.0)(webpack@5.98.0(esbuild@0.25.4)): + dependencies: + neo-async: 2.6.2 + optionalDependencies: + sass: 1.85.0 + webpack: 5.98.0(esbuild@0.25.4) + sass@1.85.0: dependencies: chokidar: 4.0.3 @@ -4911,20 +8011,91 @@ snapshots: optionalDependencies: '@parcel/watcher': 2.5.1 + sax@1.4.1: + optional: true + + schema-utils@4.3.2: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + ajv-keywords: 5.1.0(ajv@8.17.1) + + select-hose@2.0.0: {} + + selfsigned@2.4.1: + dependencies: + '@types/node-forge': 1.3.11 + node-forge: 1.3.1 + + semver@5.7.2: + optional: true + semver@6.3.1: {} semver@7.7.1: {} semver@7.7.2: {} + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + serve-index@1.9.1: + dependencies: + accepts: 1.3.8 + batch: 0.6.1 + debug: 2.6.9 + escape-html: 1.0.3 + http-errors: 1.6.3 + mime-types: 2.1.35 + parseurl: 1.3.3 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.1.0: {} + setprototypeof@1.2.0: {} + shallow-clone@3.0.1: + dependencies: + kind-of: 6.0.3 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + shell-quote@1.8.3: {} + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -4968,6 +8139,8 @@ snapshots: transitivePeerDependencies: - supports-color + slash@5.1.0: {} + slice-ansi@5.0.0: dependencies: ansi-styles: 6.2.1 @@ -5010,6 +8183,12 @@ snapshots: - supports-color - utf-8-validate + sockjs@0.3.24: + dependencies: + faye-websocket: 0.11.4 + uuid: 8.3.2 + websocket-driver: 0.7.4 + socks-proxy-agent@8.0.5: dependencies: agent-base: 7.1.3 @@ -5025,6 +8204,12 @@ snapshots: source-map-js@1.2.1: {} + source-map-loader@5.0.0(webpack@5.98.0(esbuild@0.25.4)): + dependencies: + iconv-lite: 0.6.3 + source-map-js: 1.2.1 + webpack: 5.98.0(esbuild@0.25.4) + source-map-support@0.5.21: dependencies: buffer-from: 1.1.2 @@ -5048,6 +8233,27 @@ snapshots: spdx-license-ids@3.0.21: {} + spdy-transport@3.0.0: + dependencies: + debug: 4.4.1 + detect-node: 2.1.0 + hpack.js: 2.1.6 + obuf: 1.1.2 + readable-stream: 3.6.2 + wbuf: 1.7.3 + transitivePeerDependencies: + - supports-color + + spdy@4.0.2: + dependencies: + debug: 4.4.1 + handle-thing: 2.0.1 + http-deceiver: 1.2.7 + select-hose: 2.0.0 + spdy-transport: 3.0.0 + transitivePeerDependencies: + - supports-color + sprintf-js@1.1.3: {} ssri@12.0.0: @@ -5084,6 +8290,10 @@ snapshots: get-east-asian-width: 1.3.0 strip-ansi: 7.1.0 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -5100,10 +8310,16 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} symbol-observable@4.0.0: {} + tapable@2.2.2: {} + tar@6.2.1: dependencies: chownr: 2.0.0 @@ -5122,6 +8338,30 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 + terser-webpack-plugin@5.3.14(esbuild@0.25.4)(webpack@5.98.0): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + jest-worker: 27.5.1 + schema-utils: 4.3.2 + serialize-javascript: 6.0.2 + terser: 5.39.0 + webpack: 5.98.0(esbuild@0.25.4) + optionalDependencies: + esbuild: 0.25.4 + + terser@5.39.0: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + thingies@1.21.0(tslib@2.8.1): + dependencies: + tslib: 2.8.1 + + thunky@1.1.0: {} + tinyglobby@0.2.14: dependencies: fdir: 6.4.6(picomatch@4.0.2) @@ -5139,6 +8379,12 @@ snapshots: toidentifier@1.0.1: {} + tree-dump@1.0.3(tslib@2.8.1): + dependencies: + tslib: 2.8.1 + + tree-kill@1.2.2: {} + tslib@2.8.1: {} tuf-js@3.0.1: @@ -5156,12 +8402,27 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 + typed-assert@1.0.9: {} + typescript@5.8.3: {} ua-parser-js@0.7.40: {} undici-types@7.8.0: {} + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.1.0 + + unicode-match-property-value-ecmascript@2.2.0: {} + + unicode-property-aliases-ecmascript@2.1.0: {} + + unicorn-magic@0.3.0: {} + unique-filename@4.0.0: dependencies: unique-slug: 5.0.0 @@ -5184,6 +8445,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@8.3.2: {} + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 @@ -5193,7 +8456,7 @@ snapshots: vary@1.1.2: {} - vite@6.2.7(@types/node@24.0.3)(sass@1.85.0): + vite@6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0): dependencies: esbuild: 0.25.4 postcss: 8.5.6 @@ -5201,7 +8464,10 @@ snapshots: optionalDependencies: '@types/node': 24.0.3 fsevents: 2.3.3 + jiti: 1.21.7 + less: 4.2.2 sass: 1.85.0 + terser: 5.39.0 void-elements@2.0.1: {} @@ -5210,6 +8476,10 @@ snapshots: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + wbuf@1.7.3: + dependencies: + minimalistic-assert: 1.0.1 + wcwidth@1.0.1: dependencies: defaults: 1.0.4 @@ -5217,6 +8487,106 @@ snapshots: weak-lru-cache@1.2.2: optional: true + webpack-dev-middleware@7.4.2(webpack@5.98.0): + dependencies: + colorette: 2.0.20 + memfs: 4.17.2 + mime-types: 2.1.35 + on-finished: 2.4.1 + range-parser: 1.2.1 + schema-utils: 4.3.2 + optionalDependencies: + webpack: 5.98.0(esbuild@0.25.4) + + webpack-dev-server@5.2.2(webpack@5.98.0): + dependencies: + '@types/bonjour': 3.5.13 + '@types/connect-history-api-fallback': 1.5.4 + '@types/express': 4.17.23 + '@types/express-serve-static-core': 4.19.6 + '@types/serve-index': 1.9.4 + '@types/serve-static': 1.15.8 + '@types/sockjs': 0.3.36 + '@types/ws': 8.18.1 + ansi-html-community: 0.0.8 + bonjour-service: 1.3.0 + chokidar: 3.6.0 + colorette: 2.0.20 + compression: 1.8.0 + connect-history-api-fallback: 2.0.0 + express: 4.21.2 + graceful-fs: 4.2.11 + http-proxy-middleware: 2.0.9(@types/express@4.17.23) + ipaddr.js: 2.2.0 + launch-editor: 2.10.0 + open: 10.1.0 + p-retry: 6.2.1 + schema-utils: 4.3.2 + selfsigned: 2.4.1 + serve-index: 1.9.1 + sockjs: 0.3.24 + spdy: 4.0.2 + webpack-dev-middleware: 7.4.2(webpack@5.98.0) + ws: 8.18.2 + optionalDependencies: + webpack: 5.98.0(esbuild@0.25.4) + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + webpack-merge@6.0.1: + dependencies: + clone-deep: 4.0.1 + flat: 5.0.2 + wildcard: 2.0.1 + + webpack-sources@3.3.2: {} + + webpack-subresource-integrity@5.1.0(webpack@5.98.0(esbuild@0.25.4)): + dependencies: + typed-assert: 1.0.9 + webpack: 5.98.0(esbuild@0.25.4) + + webpack@5.98.0(esbuild@0.25.4): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.15.0 + browserslist: 4.25.0 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.18.1 + es-module-lexer: 1.7.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.2 + tapable: 2.2.2 + terser-webpack-plugin: 5.3.14(esbuild@0.25.4)(webpack@5.98.0) + watchpack: 2.4.2 + webpack-sources: 3.3.2 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + websocket-driver@0.7.4: + dependencies: + http-parser-js: 0.5.10 + safe-buffer: 5.2.1 + websocket-extensions: 0.1.4 + + websocket-extensions@0.1.4: {} + which@1.3.1: dependencies: isexe: 2.0.0 @@ -5229,6 +8599,8 @@ snapshots: dependencies: isexe: 3.1.1 + wildcard@2.0.1: {} + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 @@ -5257,6 +8629,8 @@ snapshots: ws@8.17.1: {} + ws@8.18.2: {} + y18n@5.0.8: {} yallist@3.1.1: {} @@ -5289,6 +8663,8 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yocto-queue@1.2.1: {} + yoctocolors-cjs@2.1.2: {} zone.js@0.15.1: {} diff --git a/src/Vegasco-Web/src/app/app.config.ts b/src/Vegasco-Web/src/app/app.config.ts index 3df29db..86fdfbf 100644 --- a/src/Vegasco-Web/src/app/app.config.ts +++ b/src/Vegasco-Web/src/app/app.config.ts @@ -1,15 +1,13 @@ -import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideRouter, withComponentInputBinding } from '@angular/router'; - -import { routes } from './app.routes'; -import {provideHttpClient, withInterceptors} from '@angular/common/http'; -import { provideKeycloakAngular } from './auth/auth.config'; import { includeBearerTokenInterceptor } from 'keycloak-angular'; +import { routes } from './app.routes'; +import { provideKeycloakAngular } from './auth/auth.config'; export const appConfig: ApplicationConfig = { providers: [ provideKeycloakAngular(), - provideBrowserGlobalErrorListeners(), provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes, withComponentInputBinding()), provideHttpClient(withInterceptors([includeBearerTokenInterceptor])), -- 2.49.1 From 8a0776bc336ae0fb289bc4172e0a2eda17050867 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 17:52:38 +0200 Subject: [PATCH 081/150] Add primeng --- src/Vegasco-Web/package.json | 2 + src/Vegasco-Web/pnpm-lock.yaml | 69 ++++++++++++++++++++++++++- src/Vegasco-Web/src/app/app.config.ts | 10 ++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/Vegasco-Web/package.json b/src/Vegasco-Web/package.json index 1a7decd..c69b8f8 100644 --- a/src/Vegasco-Web/package.json +++ b/src/Vegasco-Web/package.json @@ -18,7 +18,9 @@ "@angular/forms": "^19.2.14", "@angular/platform-browser": "^19.2.14", "@angular/router": "^19.2.14", + "@primeng/themes": "^19.1.3", "keycloak-angular": "^19.0.2", + "primeng": "^19.1.3", "rxjs": "~7.8.2", "tslib": "^2.8.1", "zone.js": "~0.15.1" diff --git a/src/Vegasco-Web/pnpm-lock.yaml b/src/Vegasco-Web/pnpm-lock.yaml index c2a8b1c..ed45527 100644 --- a/src/Vegasco-Web/pnpm-lock.yaml +++ b/src/Vegasco-Web/pnpm-lock.yaml @@ -26,9 +26,15 @@ importers: '@angular/router': specifier: ^19.2.14 version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@primeng/themes': + specifier: ^19.1.3 + version: 19.1.3 keycloak-angular: specifier: ^19.0.2 version: 19.0.2(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(keycloak-js@26.2.0) + primeng: + specifier: ^19.1.3 + version: 19.1.3(47ee1c247593ea8ad66380722e410532) rxjs: specifier: ~7.8.2 version: 7.8.2 @@ -193,6 +199,13 @@ packages: tailwindcss: optional: true + '@angular/cdk@19.2.18': + resolution: {integrity: sha512-aGMHOYK/VV9PhxGTUDwiu/4ozoR/RKz8cimI+QjRxEBhzn4EPqjUDSganvlhmgS7cTN3+aqozdvF/GopMRJjLg==} + peerDependencies: + '@angular/common': ^19.0.0 || ^20.0.0 + '@angular/core': ^19.0.0 || ^20.0.0 + rxjs: ^6.5.3 || ^7.4.0 + '@angular/cli@19.2.15': resolution: {integrity: sha512-YRIpARHWSOnWkHusUWTQgeUrPWMjWvtQrOkjWc6stF36z2KUzKMEng6EzUvH6sZolNSwVwOFpODEP0ut4aBkvQ==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -1419,6 +1432,17 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@primeng/themes@19.1.3': + resolution: {integrity: sha512-y4VryHHUTPWlmfR56NBANC0QPIxEngTUE/J3pGs4SJquq1n5EE/U16dxa1qW/wXqLF3jn3l/AO/4KZqGj5UuAA==} + + '@primeuix/styled@0.3.2': + resolution: {integrity: sha512-ColZes0+/WKqH4ob2x8DyNYf1NENpe5ZguOvx5yCLxaP8EIMVhLjWLO/3umJiDnQU4XXMLkn2mMHHw+fhTX/mw==} + engines: {node: '>=12.11.0'} + + '@primeuix/utils@0.3.2': + resolution: {integrity: sha512-B+nphqTQeq+i6JuICLdVWnDMjONome2sNz0xI65qIOyeB4EF12CoKRiCsxuZ5uKAkHi/0d1LqlQ9mIWRSdkavw==} + engines: {node: '>=12.11.0'} + '@rollup/rollup-android-arm-eabi@4.34.8': resolution: {integrity: sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==} cpu: [arm] @@ -3396,6 +3420,18 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} + primeng@19.1.3: + resolution: {integrity: sha512-KsrvJFblKg28kA6d4npnnABjKClmJv0CgDT/kOxFq5onQNBy4547DJzRGMba4+CMLKjHWWkYWuC+XSkPMNFrZg==} + peerDependencies: + '@angular/animations': ^19.0.0 + '@angular/cdk': ^19.0.0 + '@angular/common': ^19.0.0 + '@angular/core': ^19.0.0 + '@angular/forms': ^19.0.0 + '@angular/platform-browser': ^19.0.0 + '@angular/router': ^19.0.0 + rxjs: ^6.0.0 || ^7.8.1 + proc-log@5.0.0: resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} engines: {node: ^18.17.0 || >=20.5.0} @@ -4345,7 +4381,6 @@ snapshots: '@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) tslib: 2.8.1 - optional: true '@angular/build@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(terser@5.39.0)(typescript@5.8.3)': dependencies: @@ -4396,6 +4431,14 @@ snapshots: - tsx - yaml + '@angular/cdk@19.2.18(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)': + dependencies: + '@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) + parse5: 7.3.0 + rxjs: 7.8.2 + tslib: 2.8.1 + '@angular/cli@19.2.15(@types/node@24.0.3)(chokidar@4.0.3)': dependencies: '@angular-devkit/architect': 0.1902.15(chokidar@4.0.3) @@ -5711,6 +5754,16 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@primeng/themes@19.1.3': + dependencies: + '@primeuix/styled': 0.3.2 + + '@primeuix/styled@0.3.2': + dependencies: + '@primeuix/utils': 0.3.2 + + '@primeuix/utils@0.3.2': {} + '@rollup/rollup-android-arm-eabi@4.34.8': optional: true @@ -7810,6 +7863,20 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + primeng@19.1.3(47ee1c247593ea8ad66380722e410532): + dependencies: + '@angular/animations': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)) + '@angular/cdk': 19.2.18(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) + '@angular/forms': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@angular/platform-browser': 19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)) + '@angular/router': 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@primeuix/styled': 0.3.2 + '@primeuix/utils': 0.3.2 + rxjs: 7.8.2 + tslib: 2.8.1 + proc-log@5.0.0: {} process-nextick-args@2.0.1: {} diff --git a/src/Vegasco-Web/src/app/app.config.ts b/src/Vegasco-Web/src/app/app.config.ts index 86fdfbf..9749717 100644 --- a/src/Vegasco-Web/src/app/app.config.ts +++ b/src/Vegasco-Web/src/app/app.config.ts @@ -1,7 +1,10 @@ import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; +import { provideAnimationsAsync } from '@angular/platform-browser/animations/async'; import { provideRouter, withComponentInputBinding } from '@angular/router'; +import Lara from '@primeng/themes/lara'; import { includeBearerTokenInterceptor } from 'keycloak-angular'; +import { providePrimeNG } from 'primeng/config'; import { routes } from './app.routes'; import { provideKeycloakAngular } from './auth/auth.config'; @@ -11,5 +14,12 @@ export const appConfig: ApplicationConfig = { provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes, withComponentInputBinding()), provideHttpClient(withInterceptors([includeBearerTokenInterceptor])), + provideAnimationsAsync(), + providePrimeNG({ + theme: { + preset: Lara + }, + ripple: true + }), ] }; -- 2.49.1 From 07ab1efbe50d68b5cfb8d88c47fac5564e49c590 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 18:02:50 +0200 Subject: [PATCH 082/150] Add build step for Angular --- .drone.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.drone.yml b/.drone.yml index 01f32d3..f69441f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -10,16 +10,21 @@ trigger: - custom steps: - - name: compile + - name: compile (.NET) image: mcr.microsoft.com/dotnet/sdk:9.0-alpine environment: CI_WORKSPACE: "/drone/src" commands: - - dotnet build - volumes: - - name: dockersock - path: /var/run - + - dotnet build ./vegasco-server.slnx + + - name: compile (Angular) + image: node:lts + commands: + - npm install -g pnpm + - cd src/Vegasco-Web + - pnpm install + - pnpm build + - name: test image: quay.io/testcontainers/dind-drone-plugin environment: -- 2.49.1 From 354d28d16772ca56a26a84b262e6a79c227dc3a7 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 18:03:33 +0200 Subject: [PATCH 083/150] Fix dependencies --- .drone.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index f69441f..dd28f5c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -37,7 +37,7 @@ steps: - name: dockersock path: /var/run depends_on: - - compile + - compile (.NET) - name: docker build and push image: docker:24.0.7 @@ -61,7 +61,7 @@ steps: branch: - main depends_on: - - compile + - compile (.NET) - test - name: Telegram notification @@ -75,7 +75,8 @@ steps: status: - failure depends_on: - - compile + - compile (.NET) + - compile (Angular) - test - docker build and push -- 2.49.1 From 70acaf9738e1fda61a47e83cb52e675938c1206b Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 18:20:17 +0200 Subject: [PATCH 084/150] Copy overview page structure from weight tracker --- .../entries/entries/entries.component.html | 61 ++++++++++++------- .../entries/entries/entries.component.ts | 33 ++++++++-- 2 files changed, 69 insertions(+), 25 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html index ab67d54..7cd05d7 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html @@ -1,22 +1,41 @@ -@if (consumptionEntries$ | async; as consumptionEntries) { -
- - - - - - - - - - @for (entry of consumptionEntries; track entry.id) { - - - - - +
+ +
+
+ +
+
+ + + +
+
+
+ @if (filteredWeightEntries$ | async; as weightEntries) { + + +
+ @for (weightEntry of entries; track weightEntry.id) { + + } +
+
+
+ } @else { +
+ @for (_ of skeletonsIterationSource; track $index) { + } -
-
DatumDistanzMenge
{{ entry.dateTime | date }}{{ entry.distance }} km{{ entry.amount }} l
-
-} +
+ } +
+ diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index 7015883..1bc6dc1 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -1,12 +1,37 @@ -import { AsyncPipe, DatePipe } from '@angular/common'; -import { HttpClient } from '@angular/common/http'; +import { CommonModule } from '@angular/common'; import { Component, inject } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { RouterLink } from '@angular/router'; +import { MessageService } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; +import { DataViewModule } from 'primeng/dataview'; +import { SelectModule } from 'primeng/select'; +import { ScrollTopModule } from 'primeng/scrolltop'; +import { SkeletonModule } from 'primeng/skeleton'; +import { + BehaviorSubject, + combineLatest, + map, + Observable, + of, + startWith, + tap, +} from 'rxjs'; +import { HttpClient } from '@angular/common/http'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { map, Observable, tap } from 'rxjs'; @Component({ selector: 'app-entries', - imports: [AsyncPipe, DatePipe], + imports: [ + ButtonModule, + CommonModule, + DataViewModule, + SkeletonModule, + SelectModule, + ReactiveFormsModule, + RouterLink, + ScrollTopModule, + ], templateUrl: './entries.component.html', styleUrl: './entries.component.scss' }) -- 2.49.1 From 297af2b95dd64e07a10f92257d8f4e452cfdabf3 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 18:49:07 +0200 Subject: [PATCH 085/150] Copy more from weight tracker --- .../edit-entry/edit-entry.component.html | 89 +++++++++++++++++++ .../edit-entry/edit-entry.component.scss | 0 .../edit-entry/edit-entry.component.ts | 50 +++++++++++ .../entries/entries/entries.component.html | 45 +++++----- .../entries/entries/entries.component.ts | 31 +++---- 5 files changed, 179 insertions(+), 36 deletions(-) create mode 100644 src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html create mode 100644 src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.scss create mode 100644 src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html new file mode 100644 index 0000000..d5ba414 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html @@ -0,0 +1,89 @@ +@if (!loaded) { + +} @else { +
+ +
+
+ + +
+ +
+ + +
+
+ +
+ + @if (rabbits$ | async; as rabbits) { + + } +
+ +
+ + @if (medicines$ | async; as medicines) { + + + {{ medicine.name }} ({{ medicine.abbreviation }}) + + + } +
+ +
+ + + + g + +
+ +
+ + +
+ +
+ + +
+ +
+} diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.scss b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts new file mode 100644 index 0000000..1b0d5ba --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts @@ -0,0 +1,50 @@ +import { AsyncPipe } from '@angular/common'; +import { Component, inject } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ButtonModule } from 'primeng/button'; +import { ChipModule } from 'primeng/chip'; +import { DatePickerModule } from 'primeng/datepicker'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { InputGroupModule } from 'primeng/inputgroup'; +import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; +import { InputNumberModule } from 'primeng/inputnumber'; +import { InputTextModule } from 'primeng/inputtext'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { SelectModule } from 'primeng/select'; +import { SkeletonModule } from 'primeng/skeleton'; + +@Component({ + selector: 'app-edit-entry', + imports: [ + AsyncPipe, + ButtonModule, + ChipModule, + DatePickerModule, + FloatLabelModule, + InputGroupAddonModule, + InputGroupModule, + InputNumberModule, + InputTextModule, + MultiSelectModule, + ReactiveFormsModule, + SelectModule, + SkeletonModule, + ], + templateUrl: './edit-entry.component.html', + styleUrl: './edit-entry.component.scss' +}) +export class EditEntryComponent { + private readonly formBuilder = inject(FormBuilder); + + protected readonly formGroup = this.formBuilder.group({ + date: [null], + time: [null], + rabbit: [null, Validators.required], + weight: [ + null, + Validators.min(1), + ], + medicines: [[]], + comment: [null], + }); +} diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html index 7cd05d7..f10990b 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html @@ -2,34 +2,37 @@
- +
- +
- @if (filteredWeightEntries$ | async; as weightEntries) { - - -
- @for (weightEntry of entries; track weightEntry.id) { - - } -
-
-
+ @if (consumptionEntries$ | async; as entries) { + + +
+ @for (entry of entries; track entry.id) { + {{ entry | json }} + + } +
+
+
} @else {
@for (_ of skeletonsIterationSource; track $index) { diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index 1bc6dc1..e024121 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -1,24 +1,19 @@ import { CommonModule } from '@angular/common'; import { Component, inject } from '@angular/core'; -import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ReactiveFormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; -import { MessageService } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; import { DataViewModule } from 'primeng/dataview'; -import { SelectModule } from 'primeng/select'; import { ScrollTopModule } from 'primeng/scrolltop'; +import { SelectModule } from 'primeng/select'; import { SkeletonModule } from 'primeng/skeleton'; import { - BehaviorSubject, - combineLatest, map, Observable, - of, - startWith, - tap, + tap } from 'rxjs'; -import { HttpClient } from '@angular/common/http'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { Client, GetConsumptions_ResponseDto } from '../../../shared/api/swagger.generated'; @Component({ selector: 'app-entries', @@ -36,18 +31,24 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; styleUrl: './entries.component.scss' }) export class EntriesComponent { - private readonly http = inject(HttpClient); + private readonly client = inject(Client); - protected readonly consumptionEntries$: Observable; + protected readonly consumptionEntries$: Observable; + + protected readonly rowsPerPageDefaultOption = 25; + protected readonly rowsPerPageOptions = [10, 25, 50, 100]; + protected readonly currentPageReportTemplate = '{currentPage} / {totalPages}'; + + protected readonly skeletonsIterationSource = Array(10).fill(0); constructor() { - this.consumptionEntries$ = this.http.get('/api/v1/consumptions') + this.consumptionEntries$ = this.client.consumptionsGET() .pipe( takeUntilDestroyed(), tap((response) => { console.log('Entries response:', response); }), - map(response => response.consumptions) - ); + map((response) => response.consumptions) + ) } } -- 2.49.1 From cba564a811b76c4d22d6ec41f85a12ecb00bcdec Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 19:52:04 +0200 Subject: [PATCH 086/150] Switch to Scalar swagger ui --- .../Common/StartupExtensions.cs | 15 +++------------ .../Properties/launchSettings.json | 2 +- src/Vegasco.Server.Api/Vegasco.Server.Api.csproj | 2 +- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/Vegasco.Server.Api/Common/StartupExtensions.cs b/src/Vegasco.Server.Api/Common/StartupExtensions.cs index ada640e..ac96a37 100644 --- a/src/Vegasco.Server.Api/Common/StartupExtensions.cs +++ b/src/Vegasco.Server.Api/Common/StartupExtensions.cs @@ -1,4 +1,5 @@ using Asp.Versioning.ApiExplorer; +using Scalar.AspNetCore; using Vegasco.Server.Api.Endpoints; using Vegasco.Server.ServiceDefaults; @@ -36,18 +37,8 @@ internal static class StartupExtensions if (app.Environment.IsDevelopment()) { - app.MapOpenApi("/swagger/{documentName}/swagger.json"); - app.UseSwaggerUI(o => - { - // Create a Swagger endpoint for each API version - IReadOnlyList apiVersions = app.DescribeApiVersions(); - foreach (ApiVersionDescription apiVersionDescription in apiVersions) - { - string url = $"/swagger/{apiVersionDescription.GroupName}/swagger.json"; - string name = apiVersionDescription.GroupName.ToUpperInvariant(); - o.SwaggerEndpoint(url, name); - } - }); + app.MapOpenApi(); + app.MapScalarApiReference(); } return app; diff --git a/src/Vegasco.Server.Api/Properties/launchSettings.json b/src/Vegasco.Server.Api/Properties/launchSettings.json index 0dec4f4..474c37d 100644 --- a/src/Vegasco.Server.Api/Properties/launchSettings.json +++ b/src/Vegasco.Server.Api/Properties/launchSettings.json @@ -3,7 +3,7 @@ "https": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "swagger", + "launchUrl": "scalar/v1", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, diff --git a/src/Vegasco.Server.Api/Vegasco.Server.Api.csproj b/src/Vegasco.Server.Api/Vegasco.Server.Api.csproj index 6b8436f..86548ed 100644 --- a/src/Vegasco.Server.Api/Vegasco.Server.Api.csproj +++ b/src/Vegasco.Server.Api/Vegasco.Server.Api.csproj @@ -29,9 +29,9 @@ + - -- 2.49.1 From b989c43ec3118b2a186f02491fdb47cb33bdb793 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 19:54:45 +0200 Subject: [PATCH 087/150] Revert to using manually created api classes --- .../entries/entries/entries.component.ts | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index e024121..1bc6dc1 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -1,19 +1,24 @@ import { CommonModule } from '@angular/common'; import { Component, inject } from '@angular/core'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { ReactiveFormsModule } from '@angular/forms'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; +import { MessageService } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; import { DataViewModule } from 'primeng/dataview'; -import { ScrollTopModule } from 'primeng/scrolltop'; import { SelectModule } from 'primeng/select'; +import { ScrollTopModule } from 'primeng/scrolltop'; import { SkeletonModule } from 'primeng/skeleton'; import { + BehaviorSubject, + combineLatest, map, Observable, - tap + of, + startWith, + tap, } from 'rxjs'; -import { Client, GetConsumptions_ResponseDto } from '../../../shared/api/swagger.generated'; +import { HttpClient } from '@angular/common/http'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Component({ selector: 'app-entries', @@ -31,24 +36,18 @@ import { Client, GetConsumptions_ResponseDto } from '../../../shared/api/swagger styleUrl: './entries.component.scss' }) export class EntriesComponent { - private readonly client = inject(Client); + private readonly http = inject(HttpClient); - protected readonly consumptionEntries$: Observable; - - protected readonly rowsPerPageDefaultOption = 25; - protected readonly rowsPerPageOptions = [10, 25, 50, 100]; - protected readonly currentPageReportTemplate = '{currentPage} / {totalPages}'; - - protected readonly skeletonsIterationSource = Array(10).fill(0); + protected readonly consumptionEntries$: Observable; constructor() { - this.consumptionEntries$ = this.client.consumptionsGET() + this.consumptionEntries$ = this.http.get('/api/v1/consumptions') .pipe( takeUntilDestroyed(), tap((response) => { console.log('Entries response:', response); }), - map((response) => response.consumptions) - ) + map(response => response.consumptions) + ); } } -- 2.49.1 From bcbf76fda678adfbc26e48941e67196c03772d03 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 20:28:37 +0200 Subject: [PATCH 088/150] Specify API returns types for swagger --- src/Vegasco.Server.Api/Cars/CreateCar.cs | 4 +++- src/Vegasco.Server.Api/Cars/DeleteCar.cs | 4 +++- src/Vegasco.Server.Api/Cars/GetCar.cs | 10 +++++++--- src/Vegasco.Server.Api/Cars/GetCars.cs | 5 +++-- src/Vegasco.Server.Api/Cars/UpdateCar.cs | 5 ++++- .../Consumptions/CreateConsumption.cs | 3 ++- .../Consumptions/DeleteConsumptions.cs | 4 +++- src/Vegasco.Server.Api/Consumptions/GetConsumption.cs | 4 +++- src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs | 5 +++-- .../Consumptions/UpdateConsumption.cs | 5 ++++- 10 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/Vegasco.Server.Api/Cars/CreateCar.cs b/src/Vegasco.Server.Api/Cars/CreateCar.cs index d254b6b..07fcc5e 100644 --- a/src/Vegasco.Server.Api/Cars/CreateCar.cs +++ b/src/Vegasco.Server.Api/Cars/CreateCar.cs @@ -16,7 +16,9 @@ public static class CreateCar { return builder .MapPost("cars", Endpoint) - .WithTags("Cars"); + .WithTags("Cars") + .Produces(201) + .ProducesValidationProblem(); } public class Validator : AbstractValidator diff --git a/src/Vegasco.Server.Api/Cars/DeleteCar.cs b/src/Vegasco.Server.Api/Cars/DeleteCar.cs index c78eff0..e3b2777 100644 --- a/src/Vegasco.Server.Api/Cars/DeleteCar.cs +++ b/src/Vegasco.Server.Api/Cars/DeleteCar.cs @@ -8,7 +8,9 @@ public static class DeleteCar { return builder .MapDelete("cars/{id:guid}", Endpoint) - .WithTags("Cars"); + .WithTags("Cars") + .Produces(204) + .Produces(404); } public static async Task Endpoint( diff --git a/src/Vegasco.Server.Api/Cars/GetCar.cs b/src/Vegasco.Server.Api/Cars/GetCar.cs index 9fd6955..c4b401c 100644 --- a/src/Vegasco.Server.Api/Cars/GetCar.cs +++ b/src/Vegasco.Server.Api/Cars/GetCar.cs @@ -1,4 +1,5 @@ -using Vegasco.Server.Api.Persistence; +using Microsoft.AspNetCore.Http.HttpResults; +using Vegasco.Server.Api.Persistence; namespace Vegasco.Server.Api.Cars; @@ -10,7 +11,10 @@ public static class GetCar { return builder .MapGet("cars/{id:guid}", Endpoint) - .WithTags("Cars"); + .WithDescription("Returns a single car by ID") + .WithTags("Cars") + .Produces() + .Produces(404); } private static async Task Endpoint( @@ -28,4 +32,4 @@ public static class GetCar var response = new Response(car.Id.Value, car.Name); return TypedResults.Ok(response); } -} +} \ No newline at end of file diff --git a/src/Vegasco.Server.Api/Cars/GetCars.cs b/src/Vegasco.Server.Api/Cars/GetCars.cs index 0e009db..9f9f096 100644 --- a/src/Vegasco.Server.Api/Cars/GetCars.cs +++ b/src/Vegasco.Server.Api/Cars/GetCars.cs @@ -25,10 +25,11 @@ public static class GetCars return builder .MapGet("cars", Endpoint) .WithDescription("Returns all cars") - .WithTags("Cars"); + .WithTags("Cars") + .Produces(); } - private static async Task> Endpoint( + private static async Task Endpoint( [AsParameters] Request request, ApplicationDbContext dbContext, CancellationToken cancellationToken) diff --git a/src/Vegasco.Server.Api/Cars/UpdateCar.cs b/src/Vegasco.Server.Api/Cars/UpdateCar.cs index 2e06c52..0e890e6 100644 --- a/src/Vegasco.Server.Api/Cars/UpdateCar.cs +++ b/src/Vegasco.Server.Api/Cars/UpdateCar.cs @@ -15,7 +15,10 @@ public static class UpdateCar { return builder .MapPut("cars/{id:guid}", Endpoint) - .WithTags("Cars"); + .WithTags("Cars") + .Produces() + .ProducesValidationProblem() + .Produces(404); } public class Validator : AbstractValidator diff --git a/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs b/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs index b890632..cb892a2 100644 --- a/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs @@ -16,7 +16,8 @@ public static class CreateConsumption { return builder .MapPost("consumptions", Endpoint) - .WithTags("Consumptions"); + .WithTags("Consumptions") + .Produces(201); } public class Validator : AbstractValidator diff --git a/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs index 8b6f4e9..849ffd4 100644 --- a/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs @@ -8,7 +8,9 @@ public static class DeleteConsumption { return builder .MapDelete("consumptions/{id:guid}", Endpoint) - .WithTags("Consumptions"); + .WithTags("Consumptions") + .Produces(204) + .Produces(404); } private static async Task Endpoint( diff --git a/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs b/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs index 2f29fc8..10b86a9 100644 --- a/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs @@ -10,7 +10,9 @@ public static class GetConsumption { return builder .MapGet("consumptions/{id:guid}", Endpoint) - .WithTags("Consumptions"); + .WithTags("Consumptions") + .Produces() + .Produces(404); } private static async Task Endpoint( diff --git a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs index ad5fb76..1d1c4c7 100644 --- a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs @@ -31,7 +31,8 @@ public static class GetConsumptions return builder .MapGet("consumptions", Endpoint) .WithDescription("Returns all consumption entries") - .WithTags("Consumptions"); + .WithTags("Consumptions") + .Produces(); } private static async Task> Endpoint( @@ -44,7 +45,7 @@ public static class GetConsumptions new ResponseDto(x.Id.Value, x.DateTime, x.Distance, x.Amount, x.IgnoreInCalculation, x.CarId.Value)) .ToListAsync(cancellationToken); - var apiResponse = new ApiResponse + ApiResponse apiResponse = new() { Consumptions = consumptions }; diff --git a/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs b/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs index 868946a..974e065 100644 --- a/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs @@ -15,7 +15,10 @@ public static class UpdateConsumption { return builder .MapPut("consumptions/{id:guid}", Endpoint) - .WithTags("Consumptions"); + .WithTags("Consumptions") + .Produces() + .ProducesValidationProblem() + .Produces(404); } public class Validator : AbstractValidator -- 2.49.1 From 85052df8a57ca390155f3ef353e7d39d0f01c9ee Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 20:34:09 +0200 Subject: [PATCH 089/150] Add descriptions for endpoints for use in openapi --- src/Vegasco.Server.Api/Cars/CreateCar.cs | 1 + src/Vegasco.Server.Api/Cars/DeleteCar.cs | 1 + src/Vegasco.Server.Api/Cars/UpdateCar.cs | 1 + src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs | 1 + src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs | 1 + src/Vegasco.Server.Api/Consumptions/GetConsumption.cs | 1 + src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs | 1 + 7 files changed, 7 insertions(+) diff --git a/src/Vegasco.Server.Api/Cars/CreateCar.cs b/src/Vegasco.Server.Api/Cars/CreateCar.cs index 07fcc5e..ab453ed 100644 --- a/src/Vegasco.Server.Api/Cars/CreateCar.cs +++ b/src/Vegasco.Server.Api/Cars/CreateCar.cs @@ -17,6 +17,7 @@ public static class CreateCar return builder .MapPost("cars", Endpoint) .WithTags("Cars") + .WithDescription("Creates a new car") .Produces(201) .ProducesValidationProblem(); } diff --git a/src/Vegasco.Server.Api/Cars/DeleteCar.cs b/src/Vegasco.Server.Api/Cars/DeleteCar.cs index e3b2777..271b1a2 100644 --- a/src/Vegasco.Server.Api/Cars/DeleteCar.cs +++ b/src/Vegasco.Server.Api/Cars/DeleteCar.cs @@ -9,6 +9,7 @@ public static class DeleteCar return builder .MapDelete("cars/{id:guid}", Endpoint) .WithTags("Cars") + .WithDescription("Deletes a car by ID") .Produces(204) .Produces(404); } diff --git a/src/Vegasco.Server.Api/Cars/UpdateCar.cs b/src/Vegasco.Server.Api/Cars/UpdateCar.cs index 0e890e6..9d13ed8 100644 --- a/src/Vegasco.Server.Api/Cars/UpdateCar.cs +++ b/src/Vegasco.Server.Api/Cars/UpdateCar.cs @@ -16,6 +16,7 @@ public static class UpdateCar return builder .MapPut("cars/{id:guid}", Endpoint) .WithTags("Cars") + .WithDescription("Updates a car by ID") .Produces() .ProducesValidationProblem() .Produces(404); diff --git a/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs b/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs index cb892a2..b33a610 100644 --- a/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs @@ -17,6 +17,7 @@ public static class CreateConsumption return builder .MapPost("consumptions", Endpoint) .WithTags("Consumptions") + .WithDescription("Creates a new consumption entry") .Produces(201); } diff --git a/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs index 849ffd4..bedc45c 100644 --- a/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs @@ -9,6 +9,7 @@ public static class DeleteConsumption return builder .MapDelete("consumptions/{id:guid}", Endpoint) .WithTags("Consumptions") + .WithDescription("Deletes a consumption entry by ID") .Produces(204) .Produces(404); } diff --git a/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs b/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs index 10b86a9..a8684dd 100644 --- a/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs @@ -11,6 +11,7 @@ public static class GetConsumption return builder .MapGet("consumptions/{id:guid}", Endpoint) .WithTags("Consumptions") + .WithDescription("Returns a single consumption entry by ID") .Produces() .Produces(404); } diff --git a/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs b/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs index 974e065..cb07f24 100644 --- a/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs @@ -16,6 +16,7 @@ public static class UpdateConsumption return builder .MapPut("consumptions/{id:guid}", Endpoint) .WithTags("Consumptions") + .WithDescription("Updates a consumption entry by ID") .Produces() .ProducesValidationProblem() .Produces(404); -- 2.49.1 From 0fa5b080d8c656e0b6beba32256f96d6f95a6d60 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 21:05:07 +0200 Subject: [PATCH 090/150] Add API models and clients manually --- src/Vegasco-Web/src/app/api/api-base-path.ts | 6 ++++ .../src/app/api/cars/car-client.ts | 35 +++++++++++++++++++ src/Vegasco-Web/src/app/api/cars/car.ts | 4 +++ .../src/app/api/cars/create-car-request.ts | 3 ++ .../src/app/api/cars/get-cars-response.ts | 3 ++ .../src/app/api/cars/update-car-request.ts | 3 ++ .../api/consumptions/consumption-client.ts | 35 +++++++++++++++++++ .../consumption-entry.ts | 0 .../consumptions/create-consumption-entry.ts | 7 ++++ .../get-consumption-entries-response.ts | 0 .../consumptions/update-consumption-entry.ts | 7 ++++ src/Vegasco-Web/src/app/app.config.ts | 5 +++ 12 files changed, 108 insertions(+) create mode 100644 src/Vegasco-Web/src/app/api/api-base-path.ts create mode 100644 src/Vegasco-Web/src/app/api/cars/car-client.ts create mode 100644 src/Vegasco-Web/src/app/api/cars/car.ts create mode 100644 src/Vegasco-Web/src/app/api/cars/create-car-request.ts create mode 100644 src/Vegasco-Web/src/app/api/cars/get-cars-response.ts create mode 100644 src/Vegasco-Web/src/app/api/cars/update-car-request.ts create mode 100644 src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts rename src/Vegasco-Web/src/app/api/{models => consumptions}/consumption-entry.ts (100%) create mode 100644 src/Vegasco-Web/src/app/api/consumptions/create-consumption-entry.ts rename src/Vegasco-Web/src/app/api/{models => consumptions}/get-consumption-entries-response.ts (100%) create mode 100644 src/Vegasco-Web/src/app/api/consumptions/update-consumption-entry.ts diff --git a/src/Vegasco-Web/src/app/api/api-base-path.ts b/src/Vegasco-Web/src/app/api/api-base-path.ts new file mode 100644 index 0000000..173a941 --- /dev/null +++ b/src/Vegasco-Web/src/app/api/api-base-path.ts @@ -0,0 +1,6 @@ +import { InjectionToken } from "@angular/core"; + +/** + * The base path for all API requests, e.g. when using a proxy on the origin's address. + */ +export const API_BASE_PATH = new InjectionToken('API_BASE_PATH'); diff --git a/src/Vegasco-Web/src/app/api/cars/car-client.ts b/src/Vegasco-Web/src/app/api/cars/car-client.ts new file mode 100644 index 0000000..8039ccd --- /dev/null +++ b/src/Vegasco-Web/src/app/api/cars/car-client.ts @@ -0,0 +1,35 @@ +import {inject, Injectable} from "@angular/core"; +import {HttpClient} from '@angular/common/http'; +import {map, Observable} from 'rxjs'; +import {API_BASE_PATH} from '../api-base-path'; + +@Injectable({ + providedIn: 'root', +}) +export class CarClient { + private readonly http = inject(HttpClient); + private readonly apiBasePath = inject(API_BASE_PATH, {optional: true}); + + getAll(): Observable { + return this.http.get(`${this.apiBasePath}/v1/cars`); + } + + getSingle(id: string): Observable { + return this.http.get(`${this.apiBasePath}/v1/cars/${id}`); + } + + create(request: CreateCarRequest): Observable { + return this.http.post(`${this.apiBasePath}/v1/cars`, request); + } + + update(request: UpdateCarRequest): Observable { + return this.http.put(`${this.apiBasePath}/v1/cars`, request); + } + + delete(id: string): Observable { + return this.http.delete(`${this.apiBasePath}/v1/cars/${id}`) + .pipe( + map(_ => undefined) + ); + } +} diff --git a/src/Vegasco-Web/src/app/api/cars/car.ts b/src/Vegasco-Web/src/app/api/cars/car.ts new file mode 100644 index 0000000..ecd122a --- /dev/null +++ b/src/Vegasco-Web/src/app/api/cars/car.ts @@ -0,0 +1,4 @@ +interface Car { + id: string; + name: string; +} diff --git a/src/Vegasco-Web/src/app/api/cars/create-car-request.ts b/src/Vegasco-Web/src/app/api/cars/create-car-request.ts new file mode 100644 index 0000000..2d462f3 --- /dev/null +++ b/src/Vegasco-Web/src/app/api/cars/create-car-request.ts @@ -0,0 +1,3 @@ +interface CreateCarRequest { + name: string; +} diff --git a/src/Vegasco-Web/src/app/api/cars/get-cars-response.ts b/src/Vegasco-Web/src/app/api/cars/get-cars-response.ts new file mode 100644 index 0000000..6287ec1 --- /dev/null +++ b/src/Vegasco-Web/src/app/api/cars/get-cars-response.ts @@ -0,0 +1,3 @@ +interface GetCarsResponse { + cars: Car[]; +} diff --git a/src/Vegasco-Web/src/app/api/cars/update-car-request.ts b/src/Vegasco-Web/src/app/api/cars/update-car-request.ts new file mode 100644 index 0000000..f883f34 --- /dev/null +++ b/src/Vegasco-Web/src/app/api/cars/update-car-request.ts @@ -0,0 +1,3 @@ +interface UpdateCarRequest { + name: string; +} diff --git a/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts b/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts new file mode 100644 index 0000000..31aee97 --- /dev/null +++ b/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts @@ -0,0 +1,35 @@ +import {inject, Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {API_BASE_PATH} from '../api-base-path'; +import {map, Observable} from 'rxjs'; + +@Injectable({ + providedIn: 'root', +}) +export class ConsumptionClient { + private readonly http = inject(HttpClient); + private readonly apiBasePath = inject(API_BASE_PATH, {optional: true}); + + getAll(): Observable { + return this.http.get(`${this.apiBasePath}/v1/consumptions`); + } + + getSingle(id: string): Observable { + return this.http.get(`${this.apiBasePath}/v1/consumptions/${id}`); + } + + create(request: CreateCarRequest): Observable { + return this.http.post(`${this.apiBasePath}/v1/consumptions`, request); + } + + update(request: UpdateCarRequest): Observable { + return this.http.put(`${this.apiBasePath}/v1/consumptions`, request); + } + + delete(id: string): Observable { + return this.http.delete(`${this.apiBasePath}/v1/consumptions/${id}`) + .pipe( + map(_ => undefined), + ); + } +} diff --git a/src/Vegasco-Web/src/app/api/models/consumption-entry.ts b/src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts similarity index 100% rename from src/Vegasco-Web/src/app/api/models/consumption-entry.ts rename to src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts diff --git a/src/Vegasco-Web/src/app/api/consumptions/create-consumption-entry.ts b/src/Vegasco-Web/src/app/api/consumptions/create-consumption-entry.ts new file mode 100644 index 0000000..fadf32f --- /dev/null +++ b/src/Vegasco-Web/src/app/api/consumptions/create-consumption-entry.ts @@ -0,0 +1,7 @@ +interface CreateConsumptionEntry { + dateTime: string; + distance: number; + amount: number; + ignoreInCalculation: boolean; + carId: string; +} diff --git a/src/Vegasco-Web/src/app/api/models/get-consumption-entries-response.ts b/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-response.ts similarity index 100% rename from src/Vegasco-Web/src/app/api/models/get-consumption-entries-response.ts rename to src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-response.ts diff --git a/src/Vegasco-Web/src/app/api/consumptions/update-consumption-entry.ts b/src/Vegasco-Web/src/app/api/consumptions/update-consumption-entry.ts new file mode 100644 index 0000000..fcdd0f9 --- /dev/null +++ b/src/Vegasco-Web/src/app/api/consumptions/update-consumption-entry.ts @@ -0,0 +1,7 @@ +interface UpdateConsumptionEntry { + dateTime: string; + distance: number; + amount: number; + ignoreInCalculation: boolean; + carId: string; +} diff --git a/src/Vegasco-Web/src/app/app.config.ts b/src/Vegasco-Web/src/app/app.config.ts index 9749717..ad64235 100644 --- a/src/Vegasco-Web/src/app/app.config.ts +++ b/src/Vegasco-Web/src/app/app.config.ts @@ -7,6 +7,7 @@ import { includeBearerTokenInterceptor } from 'keycloak-angular'; import { providePrimeNG } from 'primeng/config'; import { routes } from './app.routes'; import { provideKeycloakAngular } from './auth/auth.config'; +import {API_BASE_PATH} from './api/api-base-path'; export const appConfig: ApplicationConfig = { providers: [ @@ -21,5 +22,9 @@ export const appConfig: ApplicationConfig = { }, ripple: true }), + { + provide: API_BASE_PATH, + useValue: '/api' + } ] }; -- 2.49.1 From 321ffc3b7caf354182c22a93e7fd14c4f066ada2 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 16 Jun 2025 21:05:48 +0200 Subject: [PATCH 091/150] Test querying all consumption entries and cars --- src/Vegasco-Web/angular.json | 3 +++ .../entries/entries/entries.component.ts | 20 ++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Vegasco-Web/angular.json b/src/Vegasco-Web/angular.json index e07b206..5e6e817 100644 --- a/src/Vegasco-Web/angular.json +++ b/src/Vegasco-Web/angular.json @@ -113,5 +113,8 @@ } } } + }, + "cli": { + "analytics": false } } diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index 1bc6dc1..dcc4e4d 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -1,4 +1,4 @@ -import { CommonModule } from '@angular/common'; +import {AsyncPipe, CommonModule} from '@angular/common'; import { Component, inject } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; @@ -19,10 +19,13 @@ import { } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import {CarClient} from '../../../api/cars/car-client'; +import {ConsumptionClient} from '../../../api/consumptions/consumption-client'; @Component({ selector: 'app-entries', imports: [ + AsyncPipe, ButtonModule, CommonModule, DataViewModule, @@ -36,12 +39,14 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; styleUrl: './entries.component.scss' }) export class EntriesComponent { - private readonly http = inject(HttpClient); + private readonly consumptionClient = inject(ConsumptionClient); + private readonly carClient = inject(CarClient); protected readonly consumptionEntries$: Observable; + protected readonly cars$: Observable; constructor() { - this.consumptionEntries$ = this.http.get('/api/v1/consumptions') + this.consumptionEntries$ = this.consumptionClient.getAll() .pipe( takeUntilDestroyed(), tap((response) => { @@ -49,5 +54,14 @@ export class EntriesComponent { }), map(response => response.consumptions) ); + + this.cars$ = this.carClient.getAll() + .pipe( + takeUntilDestroyed(), + tap((response) => { + console.log('Cars response:', response); + }), + map(response => response.cars) + ); } } -- 2.49.1 From 229bfe0b79a153b4c025f43cbc25d6342217ad7c Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Wed, 18 Jun 2025 20:54:04 +0200 Subject: [PATCH 092/150] Add import paths --- src/Vegasco-Web/src/app/api/cars/car-client.ts | 10 +++++----- src/Vegasco-Web/src/app/auth/auth.config.ts | 14 +++++++------- .../entries/entries/entries.component.ts | 18 ++++++------------ src/Vegasco-Web/tsconfig.json | 6 ++++++ 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/Vegasco-Web/src/app/api/cars/car-client.ts b/src/Vegasco-Web/src/app/api/cars/car-client.ts index 8039ccd..ee13171 100644 --- a/src/Vegasco-Web/src/app/api/cars/car-client.ts +++ b/src/Vegasco-Web/src/app/api/cars/car-client.ts @@ -1,14 +1,14 @@ -import {inject, Injectable} from "@angular/core"; -import {HttpClient} from '@angular/common/http'; -import {map, Observable} from 'rxjs'; -import {API_BASE_PATH} from '../api-base-path'; +import { HttpClient } from '@angular/common/http'; +import { inject, Injectable } from "@angular/core"; +import { map, Observable } from 'rxjs'; +import { API_BASE_PATH } from "../api-base-path"; @Injectable({ providedIn: 'root', }) export class CarClient { private readonly http = inject(HttpClient); - private readonly apiBasePath = inject(API_BASE_PATH, {optional: true}); + private readonly apiBasePath = inject(API_BASE_PATH, { optional: true }); getAll(): Observable { return this.http.get(`${this.apiBasePath}/v1/cars`); diff --git a/src/Vegasco-Web/src/app/auth/auth.config.ts b/src/Vegasco-Web/src/app/auth/auth.config.ts index ad6227b..db5d77b 100644 --- a/src/Vegasco-Web/src/app/auth/auth.config.ts +++ b/src/Vegasco-Web/src/app/auth/auth.config.ts @@ -1,12 +1,12 @@ -import { environment } from '../../environments/environment'; +import { environment } from '@vegasco-web/environments/environment'; import { - provideKeycloak, - createInterceptorCondition, - IncludeBearerTokenCondition, - INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, - withAutoRefreshToken, AutoRefreshTokenService, - UserActivityService + createInterceptorCondition, + INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, + IncludeBearerTokenCondition, + provideKeycloak, + UserActivityService, + withAutoRefreshToken } from 'keycloak-angular'; const serverHostBearerInterceptorCondition = createInterceptorCondition({ diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index dcc4e4d..c409771 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -1,26 +1,20 @@ import {AsyncPipe, CommonModule} from '@angular/common'; import { Component, inject } from '@angular/core'; -import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { ReactiveFormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; -import { MessageService } from 'primeng/api'; +import { CarClient } from '@vegasco-web/api/cars/car-client'; +import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; import { ButtonModule } from 'primeng/button'; import { DataViewModule } from 'primeng/dataview'; -import { SelectModule } from 'primeng/select'; import { ScrollTopModule } from 'primeng/scrolltop'; +import { SelectModule } from 'primeng/select'; import { SkeletonModule } from 'primeng/skeleton'; import { - BehaviorSubject, - combineLatest, map, Observable, - of, - startWith, - tap, + tap } from 'rxjs'; -import { HttpClient } from '@angular/common/http'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import {CarClient} from '../../../api/cars/car-client'; -import {ConsumptionClient} from '../../../api/consumptions/consumption-client'; @Component({ selector: 'app-entries', diff --git a/src/Vegasco-Web/tsconfig.json b/src/Vegasco-Web/tsconfig.json index e4955f2..7d4ffae 100644 --- a/src/Vegasco-Web/tsconfig.json +++ b/src/Vegasco-Web/tsconfig.json @@ -3,6 +3,12 @@ { "compileOnSave": false, "compilerOptions": { + "baseUrl": "./", + "paths": { + "@vegasco-web/*": ["src/app/*"], + "@vegasco-web/assets/*": ["assets/*"], + "@vegasco-web/environments/*": ["src/environments/*"] + }, "strict": true, "noImplicitOverride": true, "noPropertyAccessFromIndexSignature": true, -- 2.49.1 From 73fbe30b3d3581fb41fc9140d8c3900713692d3a Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Wed, 18 Jun 2025 20:54:21 +0200 Subject: [PATCH 093/150] Add missing information on overview page for it to compile --- .../src/app/modules/entries/entries/entries.component.html | 6 +++--- .../src/app/modules/entries/entries/entries.component.ts | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html index f10990b..4110790 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html @@ -16,11 +16,11 @@ diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index c409771..2abd85d 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -1,4 +1,4 @@ -import {AsyncPipe, CommonModule} from '@angular/common'; +import { AsyncPipe, CommonModule } from '@angular/common'; import { Component, inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ReactiveFormsModule } from '@angular/forms'; @@ -39,6 +39,8 @@ export class EntriesComponent { protected readonly consumptionEntries$: Observable; protected readonly cars$: Observable; + protected readonly skeletonsIterationSource = Array(10).fill(0); + constructor() { this.consumptionEntries$ = this.consumptionClient.getAll() .pipe( -- 2.49.1 From f173d46c2ec508071fbb1056fe9d43b0d524c4c4 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Wed, 18 Jun 2025 21:15:26 +0200 Subject: [PATCH 094/150] Copy more stuff to make app compile --- .../edit-entry/edit-entry.component.html | 98 +++++------- .../edit-entry/edit-entry.component.ts | 139 ++++++++++++++++-- 2 files changed, 166 insertions(+), 71 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html index d5ba414..b24fe21 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html @@ -1,83 +1,59 @@ -@if (!loaded) { +@if (isLoading()) { } @else {
-
-
- - -
- -
- - -
-
-
- - @if (rabbits$ | async; as rabbits) { + + @if (cars(); as cars) { }
- - @if (medicines$ | async; as medicines) { - - - {{ medicine.name }} ({{ medicine.abbreviation }}) - - - } + +
- + - - g + + km
- - + + + + l +
diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts index 1b0d5ba..874326d 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts @@ -1,6 +1,8 @@ -import { AsyncPipe } from '@angular/common'; -import { Component, inject } from '@angular/core'; +import { Component, computed, inject, Signal } from '@angular/core'; +import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { CarClient } from '@vegasco-web/api/cars/car-client'; import { ButtonModule } from 'primeng/button'; import { ChipModule } from 'primeng/chip'; import { DatePickerModule } from 'primeng/datepicker'; @@ -12,11 +14,11 @@ import { InputTextModule } from 'primeng/inputtext'; import { MultiSelectModule } from 'primeng/multiselect'; import { SelectModule } from 'primeng/select'; import { SkeletonModule } from 'primeng/skeleton'; +import { map } from 'rxjs'; @Component({ selector: 'app-edit-entry', imports: [ - AsyncPipe, ButtonModule, ChipModule, DatePickerModule, @@ -35,16 +37,133 @@ import { SkeletonModule } from 'primeng/skeleton'; }) export class EditEntryComponent { private readonly formBuilder = inject(FormBuilder); + private readonly carClient = inject(CarClient); + private readonly router = inject(Router); + + protected readonly formFieldNames = { + car: 'car', + date: 'date', + mileage: 'mileage', + amount: 'amount', + } protected readonly formGroup = this.formBuilder.group({ - date: [null], - time: [null], - rabbit: [null, Validators.required], - weight: [ + [this.formFieldNames.car]: [null, Validators.required], + [this.formFieldNames.date]: [null], + [this.formFieldNames.mileage]: [ null, - Validators.min(1), + [Validators.required, Validators.min(1)], + ], + [this.formFieldNames.amount]: [ + null, + [Validators.required, Validators.min(1)], ], - medicines: [[]], - comment: [null], }); + + protected readonly cars: Signal; + + protected readonly isLoading = computed(() => { + return this.cars() === undefined; + }) + + constructor() { + this.cars = toSignal( + this.carClient + .getAll() + .pipe( + takeUntilDestroyed(), + map(response => response.cars) + ), + ); + } + + async navigateToOverviewPage(): Promise { + await this.router.navigateByUrl(`/entries`); + } + + onSubmit(): void { + // if (!this.entryId) { + // this.createEntry(); + // return; + // } + + // this.updateEntry(); + } + + createEntry() { + // this.api + // .createWeightEntry( + // this.getWeighedAt(), + // this.formGroup.controls['weight'].value, + // this.formGroup.controls['comment'].value, + // this.formGroup.controls['rabbit'].value!.id, + // this.formGroup.controls['medicines'].value?.map((x) => x.id) ?? [], + // ) + // .subscribe({ + // next: (_) => { + // this.router.navigateByUrl('/weight-entries'); + // }, + // error: (error: HttpErrorResponse) => { + // switch (true) { + // case error.status >= 500 && error.status <= 599: + // this.messageService.add({ + // severity: 'error', + // summary: 'Serverfehler', + // detail: + // 'Beim Erstellen des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.', + // }); + // break; + // case error.status == 400: + // this.messageService.add({ + // severity: 'error', + // summary: 'Clientfehler', + // detail: + // 'Die Anwendung scheint falsche Daten an den Server zu senden.', + // }); + // break; + // default: + // break; + // } + // }, + // }); + } + + updateEntry() { + // this.api + // .updateWeightEntry( + // this.entryId!, + // this.getWeighedAt(), + // this.formGroup.controls['weight'].value, + // this.formGroup.controls['comment'].value, + // this.formGroup.controls['rabbit'].value!.id, + // this.formGroup.controls['medicines'].value?.map((x) => x.id) ?? [], + // ) + // .subscribe({ + // next: (_) => { + // this.router.navigateByUrl('/weight-entries'); + // }, + // error: (error: HttpErrorResponse) => { + // switch (true) { + // case error.status >= 500 && error.status <= 599: + // this.messageService.add({ + // severity: 'error', + // summary: 'Serverfehler', + // detail: + // 'Beim Aktualisieren des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.', + // }); + // break; + // case error.status == 400: + // this.messageService.add({ + // severity: 'error', + // summary: 'Clientfehler', + // detail: + // 'Die Anwendung scheint falsche Daten an den Server zu senden.', + // }); + // break; + // default: + // break; + // } + // }, + // }); + } } -- 2.49.1 From 16318c70f79cc1e566f7aa3226f4dbff2b3a4021 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Wed, 18 Jun 2025 21:29:24 +0200 Subject: [PATCH 095/150] Add and fix routes --- src/Vegasco-Web/src/app/modules/entries/entries.routes.ts | 8 ++++++++ .../app/modules/entries/entries/entries.component.html | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries.routes.ts b/src/Vegasco-Web/src/app/modules/entries/entries.routes.ts index 598ef33..020bc21 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries.routes.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries.routes.ts @@ -5,5 +5,13 @@ export const routes: Routes = [ { path: '', component: EntriesComponent + }, + { + path: 'create', + loadComponent: () => import('./edit-entry/edit-entry.component').then(m => m.EditEntryComponent) + }, + { + path: 'edit/:id', + loadComponent: () => import('./edit-entry/edit-entry.component').then(m => m.EditEntryComponent) } ]; \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html index 4110790..df3e664 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html @@ -6,7 +6,7 @@ [options]="(rabbits$ | async)!" optionLabel="name" /> -->
- +
-- 2.49.1 From b382446828ab222e88bf857356d6ea4a60b4ce6b Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 12:33:46 +0200 Subject: [PATCH 096/150] Add styling based in Weight Tracker UI --- src/Vegasco-Web/.postcssrc.json | 5 + src/Vegasco-Web/package.json | 4 + src/Vegasco-Web/pnpm-lock.yaml | 335 +++++++++++++++++++++++++++++-- src/Vegasco-Web/src/app/app.html | 12 +- src/Vegasco-Web/src/app/app.scss | 10 + src/Vegasco-Web/src/app/app.ts | 7 +- src/Vegasco-Web/src/styles.scss | 41 +++- 7 files changed, 396 insertions(+), 18 deletions(-) create mode 100644 src/Vegasco-Web/.postcssrc.json diff --git a/src/Vegasco-Web/.postcssrc.json b/src/Vegasco-Web/.postcssrc.json new file mode 100644 index 0000000..72f908d --- /dev/null +++ b/src/Vegasco-Web/.postcssrc.json @@ -0,0 +1,5 @@ +{ + "plugins": { + "@tailwindcss/postcss": {} + } +} \ No newline at end of file diff --git a/src/Vegasco-Web/package.json b/src/Vegasco-Web/package.json index c69b8f8..f7ad866 100644 --- a/src/Vegasco-Web/package.json +++ b/src/Vegasco-Web/package.json @@ -19,9 +19,13 @@ "@angular/platform-browser": "^19.2.14", "@angular/router": "^19.2.14", "@primeng/themes": "^19.1.3", + "@tailwindcss/postcss": "^4.1.10", "keycloak-angular": "^19.0.2", + "postcss": "^8.5.6", "primeng": "^19.1.3", "rxjs": "~7.8.2", + "tailwindcss": "^4.1.10", + "tailwindcss-primeui": "^0.6.1", "tslib": "^2.8.1", "zone.js": "~0.15.1" }, diff --git a/src/Vegasco-Web/pnpm-lock.yaml b/src/Vegasco-Web/pnpm-lock.yaml index ed45527..e107ca7 100644 --- a/src/Vegasco-Web/pnpm-lock.yaml +++ b/src/Vegasco-Web/pnpm-lock.yaml @@ -29,15 +29,27 @@ importers: '@primeng/themes': specifier: ^19.1.3 version: 19.1.3 + '@tailwindcss/postcss': + specifier: ^4.1.10 + version: 4.1.10 keycloak-angular: specifier: ^19.0.2 version: 19.0.2(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(keycloak-js@26.2.0) + postcss: + specifier: ^8.5.6 + version: 8.5.6 primeng: specifier: ^19.1.3 version: 19.1.3(47ee1c247593ea8ad66380722e410532) rxjs: specifier: ~7.8.2 version: 7.8.2 + tailwindcss: + specifier: ^4.1.10 + version: 4.1.10 + tailwindcss-primeui: + specifier: ^0.6.1 + version: 0.6.1(tailwindcss@4.1.10) tslib: specifier: ^2.8.1 version: 2.8.1 @@ -47,7 +59,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: ^19.2.15 - version: 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(typescript@5.8.3)(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)) + version: 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(jiti@2.4.2)(karma@6.4.4)(lightningcss@1.30.1)(tailwindcss@4.1.10)(typescript@5.8.3)(vite@6.2.7(@types/node@24.0.3)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)) '@angular/cli': specifier: ^19.2.15 version: 19.2.15(@types/node@24.0.3)(chokidar@4.0.3) @@ -84,6 +96,10 @@ importers: packages: + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -1573,6 +1589,94 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@tailwindcss/node@4.1.10': + resolution: {integrity: sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ==} + + '@tailwindcss/oxide-android-arm64@4.1.10': + resolution: {integrity: sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.10': + resolution: {integrity: sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.10': + resolution: {integrity: sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.10': + resolution: {integrity: sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.10': + resolution: {integrity: sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.10': + resolution: {integrity: sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.10': + resolution: {integrity: sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.10': + resolution: {integrity: sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.10': + resolution: {integrity: sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.10': + resolution: {integrity: sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.10': + resolution: {integrity: sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.10': + resolution: {integrity: sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.10': + resolution: {integrity: sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.10': + resolution: {integrity: sha512-B+7r7ABZbkXJwpvt2VMnS6ujcDoR2OOcFaqrLIo1xbcdxje4Vf+VgJdBzNNbrAjBj/rLZ66/tlQ1knIGNLKOBQ==} + '@tufjs/canonical-json@2.0.0': resolution: {integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==} engines: {node: ^16.14.0 || >=18.0.0} @@ -2787,6 +2891,10 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2904,6 +3012,70 @@ packages: webpack: optional: true + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -3865,6 +4037,14 @@ packages: resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} engines: {node: '>=0.10'} + tailwindcss-primeui@0.6.1: + resolution: {integrity: sha512-T69Rylcrmnt8zy9ik+qZvsLuRIrS9/k6rYJSIgZ1trnbEzGDDQSCIdmfyZknevqiHwpSJHSmQ9XT2C+S/hJY4A==} + peerDependencies: + tailwindcss: '>=3.1.0' + + tailwindcss@4.1.10: + resolution: {integrity: sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==} + tapable@2.2.2: resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} engines: {node: '>=6'} @@ -4249,6 +4429,8 @@ packages: snapshots: + '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -4261,13 +4443,13 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(typescript@5.8.3)(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0))': + '@angular-devkit/build-angular@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(jiti@2.4.2)(karma@6.4.4)(lightningcss@1.30.1)(tailwindcss@4.1.10)(typescript@5.8.3)(vite@6.2.7(@types/node@24.0.3)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0))': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.15(chokidar@4.0.3) '@angular-devkit/build-webpack': 0.1902.15(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.98.0))(webpack@5.98.0(esbuild@0.25.4)) '@angular-devkit/core': 19.2.15(chokidar@4.0.3) - '@angular/build': 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(terser@5.39.0)(typescript@5.8.3) + '@angular/build': 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(jiti@2.4.2)(karma@6.4.4)(less@4.2.2)(lightningcss@1.30.1)(postcss@8.5.2)(tailwindcss@4.1.10)(terser@5.39.0)(typescript@5.8.3) '@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3) '@babel/core': 7.26.10 '@babel/generator': 7.26.10 @@ -4280,7 +4462,7 @@ snapshots: '@babel/runtime': 7.26.10 '@discoveryjs/json-ext': 0.6.3 '@ngtools/webpack': 19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.4)) - '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.2.7(@types/node@24.0.3)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)) ansi-colors: 4.1.3 autoprefixer: 10.4.20(postcss@8.5.2) babel-loader: 9.2.1(@babel/core@7.26.10)(webpack@5.98.0(esbuild@0.25.4)) @@ -4323,6 +4505,7 @@ snapshots: optionalDependencies: esbuild: 0.25.4 karma: 6.4.4 + tailwindcss: 4.1.10 transitivePeerDependencies: - '@angular/compiler' - '@rspack/core' @@ -4382,7 +4565,7 @@ snapshots: '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) tslib: 2.8.1 - '@angular/build@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(jiti@1.21.7)(karma@6.4.4)(less@4.2.2)(postcss@8.5.2)(terser@5.39.0)(typescript@5.8.3)': + '@angular/build@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(@angular/compiler@19.2.14)(@types/node@24.0.3)(chokidar@4.0.3)(jiti@2.4.2)(karma@6.4.4)(less@4.2.2)(lightningcss@1.30.1)(postcss@8.5.2)(tailwindcss@4.1.10)(terser@5.39.0)(typescript@5.8.3)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1902.15(chokidar@4.0.3) @@ -4393,7 +4576,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.10) '@inquirer/confirm': 5.1.6(@types/node@24.0.3) - '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0)) + '@vitejs/plugin-basic-ssl': 1.2.0(vite@6.2.7(@types/node@24.0.3)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0)) beasties: 0.3.2 browserslist: 4.25.0 esbuild: 0.25.4 @@ -4411,13 +4594,14 @@ snapshots: semver: 7.7.1 source-map-support: 0.5.21 typescript: 5.8.3 - vite: 6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0) + vite: 6.2.7(@types/node@24.0.3)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0) watchpack: 2.4.2 optionalDependencies: karma: 6.4.4 less: 4.2.2 lmdb: 3.2.6 postcss: 8.5.2 + tailwindcss: 4.1.10 transitivePeerDependencies: - '@types/node' - chokidar @@ -5865,6 +6049,78 @@ snapshots: '@socket.io/component-emitter@3.1.2': {} + '@tailwindcss/node@4.1.10': + dependencies: + '@ampproject/remapping': 2.3.0 + enhanced-resolve: 5.18.1 + jiti: 2.4.2 + lightningcss: 1.30.1 + magic-string: 0.30.17 + source-map-js: 1.2.1 + tailwindcss: 4.1.10 + + '@tailwindcss/oxide-android-arm64@4.1.10': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.10': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.10': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.10': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.10': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.10': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.10': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.10': + optional: true + + '@tailwindcss/oxide@4.1.10': + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.10 + '@tailwindcss/oxide-darwin-arm64': 4.1.10 + '@tailwindcss/oxide-darwin-x64': 4.1.10 + '@tailwindcss/oxide-freebsd-x64': 4.1.10 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.10 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.10 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.10 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.10 + '@tailwindcss/oxide-linux-x64-musl': 4.1.10 + '@tailwindcss/oxide-wasm32-wasi': 4.1.10 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.10 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.10 + + '@tailwindcss/postcss@4.1.10': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.10 + '@tailwindcss/oxide': 4.1.10 + postcss: 8.5.6 + tailwindcss: 4.1.10 + '@tufjs/canonical-json@2.0.0': {} '@tufjs/models@3.0.1': @@ -5969,9 +6225,9 @@ snapshots: dependencies: '@types/node': 24.0.3 - '@vitejs/plugin-basic-ssl@1.2.0(vite@6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0))': + '@vitejs/plugin-basic-ssl@1.2.0(vite@6.2.7(@types/node@24.0.3)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0))': dependencies: - vite: 6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0) + vite: 6.2.7(@types/node@24.0.3)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0) '@webassemblyjs/ast@1.14.1': dependencies: @@ -6504,8 +6760,7 @@ snapshots: detect-libc@1.0.3: optional: true - detect-libc@2.0.4: - optional: true + detect-libc@2.0.4: {} detect-node@2.1.0: {} @@ -7182,6 +7437,8 @@ snapshots: jiti@1.21.7: {} + jiti@2.4.2: {} + js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -7315,6 +7572,51 @@ snapshots: optionalDependencies: webpack: 5.98.0(esbuild@0.25.4) + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + lines-and-columns@1.2.4: {} listr2@8.2.5: @@ -8385,6 +8687,12 @@ snapshots: symbol-observable@4.0.0: {} + tailwindcss-primeui@0.6.1(tailwindcss@4.1.10): + dependencies: + tailwindcss: 4.1.10 + + tailwindcss@4.1.10: {} + tapable@2.2.2: {} tar@6.2.1: @@ -8523,7 +8831,7 @@ snapshots: vary@1.1.2: {} - vite@6.2.7(@types/node@24.0.3)(jiti@1.21.7)(less@4.2.2)(sass@1.85.0)(terser@5.39.0): + vite@6.2.7(@types/node@24.0.3)(jiti@2.4.2)(less@4.2.2)(lightningcss@1.30.1)(sass@1.85.0)(terser@5.39.0): dependencies: esbuild: 0.25.4 postcss: 8.5.6 @@ -8531,8 +8839,9 @@ snapshots: optionalDependencies: '@types/node': 24.0.3 fsevents: 2.3.3 - jiti: 1.21.7 + jiti: 2.4.2 less: 4.2.2 + lightningcss: 1.30.1 sass: 1.85.0 terser: 5.39.0 diff --git a/src/Vegasco-Web/src/app/app.html b/src/Vegasco-Web/src/app/app.html index 530a5f8..0954df0 100644 --- a/src/Vegasco-Web/src/app/app.html +++ b/src/Vegasco-Web/src/app/app.html @@ -1,5 +1,13 @@
-
- +
+ +
+
+ +
diff --git a/src/Vegasco-Web/src/app/app.scss b/src/Vegasco-Web/src/app/app.scss index e69de29..01e3fdc 100644 --- a/src/Vegasco-Web/src/app/app.scss +++ b/src/Vegasco-Web/src/app/app.scss @@ -0,0 +1,10 @@ +.content { + padding: 1rem; +} + +.header { + padding: 0 1rem; + display: flex; + align-items: center; + height: 100%; +} diff --git a/src/Vegasco-Web/src/app/app.ts b/src/Vegasco-Web/src/app/app.ts index 98798e3..611b7ec 100644 --- a/src/Vegasco-Web/src/app/app.ts +++ b/src/Vegasco-Web/src/app/app.ts @@ -1,12 +1,15 @@ import { Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; +import { MessageService } from 'primeng/api'; +import { ToastModule } from 'primeng/toast'; + @Component({ selector: 'app-root', - imports: [RouterOutlet], + imports: [RouterOutlet, ToastModule], + providers: [MessageService], templateUrl: './app.html', styleUrl: './app.scss' }) export class App { - protected title = 'Vegasco-Web'; } diff --git a/src/Vegasco-Web/src/styles.scss b/src/Vegasco-Web/src/styles.scss index 90d4ee0..18fb1cc 100644 --- a/src/Vegasco-Web/src/styles.scss +++ b/src/Vegasco-Web/src/styles.scss @@ -1 +1,40 @@ -/* You can add global styles to this file, and also import other style files */ +@import "tailwindcss"; +@import "tailwindcss"; +@plugin "tailwindcss-primeui"; + +html, +body { + height: 100%; + margin: 0; +} + +body { + font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif +} + +.max-content-width { + max-width: 1200px; +} + +.pos-absolute { + position: absolute; +} + +.pos-relative { + position: relative; +} + +.trbl-0 { + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +.primary-color-text { + color: var(--primary-color-text); +} + +.visually-hidden { + visibility: hidden; +} -- 2.49.1 From 0cf9f3cd0f3e72d2ada53fb2cf74caab9ce5c81f Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 12:45:17 +0200 Subject: [PATCH 097/150] Fix dropdown value mapping --- .../app/modules/entries/edit-entry/edit-entry.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html index b24fe21..231d154 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html @@ -10,7 +10,7 @@ [options]="cars" placeholder="Auto auswählen" [formControlName]="formFieldNames.car" - [optionLabel]="formFieldNames.car" + optionLabel="name" [inputId]="formFieldNames.car" styleClass="w-full" /> } -- 2.49.1 From f0998c818a896e22bde9b120a2bc41127cee9b8b Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 12:45:28 +0200 Subject: [PATCH 098/150] Add required marker for required form field --- .../components/required-marker.component.html | 1 + .../components/required-marker.component.scss | 3 +++ .../components/required-marker.component.ts | 11 +++++++++++ .../edit-entry/edit-entry.component.html | 19 +++++++++++++++---- .../edit-entry/edit-entry.component.ts | 2 ++ 5 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 src/Vegasco-Web/src/app/modules/entries/edit-entry/components/required-marker.component.html create mode 100644 src/Vegasco-Web/src/app/modules/entries/edit-entry/components/required-marker.component.scss create mode 100644 src/Vegasco-Web/src/app/modules/entries/edit-entry/components/required-marker.component.ts diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/components/required-marker.component.html b/src/Vegasco-Web/src/app/modules/entries/edit-entry/components/required-marker.component.html new file mode 100644 index 0000000..a01d1a4 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/components/required-marker.component.html @@ -0,0 +1 @@ +* diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/components/required-marker.component.scss b/src/Vegasco-Web/src/app/modules/entries/edit-entry/components/required-marker.component.scss new file mode 100644 index 0000000..00a2ac1 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/components/required-marker.component.scss @@ -0,0 +1,3 @@ +.required { + color: red; +} \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/components/required-marker.component.ts b/src/Vegasco-Web/src/app/modules/entries/edit-entry/components/required-marker.component.ts new file mode 100644 index 0000000..5017a19 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/components/required-marker.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-required-marker', + imports: [ + ], + templateUrl: './required-marker.component.html', + styleUrl: './required-marker.component.scss' +}) +export class RequiredMarkerComponent { +} diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html index 231d154..09eba02 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html @@ -4,7 +4,10 @@
- + @if (cars(); as cars) {
- +
- +
- + Date: Thu, 19 Jun 2025 12:45:38 +0200 Subject: [PATCH 099/150] Add special liter l for unit --- .../app/modules/entries/edit-entry/edit-entry.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html index 09eba02..0c5f2a7 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html @@ -63,7 +63,7 @@ min="1" pInputText [formControlName]="formFieldNames.amount" /> - l +
-- 2.49.1 From b323f7a29ff6ddf048e35b1ea447185891a4f719 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 13:38:15 +0200 Subject: [PATCH 100/150] Fix api clients --- src/Vegasco-Web/src/app/api/cars/car-client.ts | 4 ++-- .../src/app/api/consumptions/consumption-client.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Vegasco-Web/src/app/api/cars/car-client.ts b/src/Vegasco-Web/src/app/api/cars/car-client.ts index ee13171..ee0be41 100644 --- a/src/Vegasco-Web/src/app/api/cars/car-client.ts +++ b/src/Vegasco-Web/src/app/api/cars/car-client.ts @@ -22,8 +22,8 @@ export class CarClient { return this.http.post(`${this.apiBasePath}/v1/cars`, request); } - update(request: UpdateCarRequest): Observable { - return this.http.put(`${this.apiBasePath}/v1/cars`, request); + update(id: string, request: UpdateCarRequest): Observable { + return this.http.put(`${this.apiBasePath}/v1/cars/${id}`, request); } delete(id: string): Observable { diff --git a/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts b/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts index 31aee97..1646cba 100644 --- a/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts +++ b/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts @@ -18,12 +18,12 @@ export class ConsumptionClient { return this.http.get(`${this.apiBasePath}/v1/consumptions/${id}`); } - create(request: CreateCarRequest): Observable { + create(request: CreateConsumptionEntry): Observable { return this.http.post(`${this.apiBasePath}/v1/consumptions`, request); } - update(request: UpdateCarRequest): Observable { - return this.http.put(`${this.apiBasePath}/v1/consumptions`, request); + update(id: string, request: UpdateConsumptionEntry): Observable { + return this.http.put(`${this.apiBasePath}/v1/consumptions/${id}`, request); } delete(id: string): Observable { -- 2.49.1 From 390241aa536daf99e16a6e91695013955297c5f9 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 13:38:29 +0200 Subject: [PATCH 101/150] Add sending entries to api --- .../edit-entry/edit-entry.component.ts | 162 +++++++++--------- 1 file changed, 82 insertions(+), 80 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts index ff43f2d..a0e0b68 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts @@ -1,4 +1,4 @@ -import { Component, computed, inject, Signal } from '@angular/core'; +import { Component, computed, DestroyRef, inject, input, Signal } from '@angular/core'; import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { Router } from '@angular/router'; @@ -14,8 +14,11 @@ import { InputTextModule } from 'primeng/inputtext'; import { MultiSelectModule } from 'primeng/multiselect'; import { SelectModule } from 'primeng/select'; import { SkeletonModule } from 'primeng/skeleton'; -import { map } from 'rxjs'; +import { catchError, EMPTY, map, Observable, switchMap, tap, throwError } from 'rxjs'; import { RequiredMarkerComponent } from './components/required-marker.component'; +import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; +import { HttpErrorResponse } from '@angular/common/http'; +import { MessageService } from 'primeng/api'; @Component({ selector: 'app-edit-entry', @@ -40,14 +43,19 @@ import { RequiredMarkerComponent } from './components/required-marker.component' export class EditEntryComponent { private readonly formBuilder = inject(FormBuilder); private readonly carClient = inject(CarClient); + private readonly consumptionClient = inject(ConsumptionClient); private readonly router = inject(Router); + private readonly destroyRef = inject(DestroyRef); + private readonly messageService = inject(MessageService); + + protected readonly entryId = input(undefined); protected readonly formFieldNames = { car: 'car', date: 'date', mileage: 'mileage', amount: 'amount', - } + } as const; protected readonly formGroup = this.formBuilder.group({ [this.formFieldNames.car]: [null, Validators.required], @@ -84,88 +92,82 @@ export class EditEntryComponent { } onSubmit(): void { - // if (!this.entryId) { - // this.createEntry(); - // return; - // } + var entryId = this.entryId(); + if (entryId === undefined || entryId === null) { + this.createEntry(); + return; + } - // this.updateEntry(); + this.updateEntry(entryId); + } + + private getFormData() { + var dateTime = new Date((this.formGroup.controls[this.formFieldNames.date].value ?? new Date).setHours(0, 0, 0, 0)); + + return { + carId: this.formGroup.controls[this.formFieldNames.car].value!.id, + dateTime: dateTime.toISOString(), + distance: this.formGroup.controls[this.formFieldNames.mileage].value!, + amount: this.formGroup.controls[this.formFieldNames.amount].value!, + ignoreInCalculation: false, + }; } createEntry() { - // this.api - // .createWeightEntry( - // this.getWeighedAt(), - // this.formGroup.controls['weight'].value, - // this.formGroup.controls['comment'].value, - // this.formGroup.controls['rabbit'].value!.id, - // this.formGroup.controls['medicines'].value?.map((x) => x.id) ?? [], - // ) - // .subscribe({ - // next: (_) => { - // this.router.navigateByUrl('/weight-entries'); - // }, - // error: (error: HttpErrorResponse) => { - // switch (true) { - // case error.status >= 500 && error.status <= 599: - // this.messageService.add({ - // severity: 'error', - // summary: 'Serverfehler', - // detail: - // 'Beim Erstellen des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.', - // }); - // break; - // case error.status == 400: - // this.messageService.add({ - // severity: 'error', - // summary: 'Clientfehler', - // detail: - // 'Die Anwendung scheint falsche Daten an den Server zu senden.', - // }); - // break; - // default: - // break; - // } - // }, - // }); + var request: CreateConsumptionEntry = this.getFormData(); + this.consumptionClient.create(request) + .pipe( + takeUntilDestroyed(this.destroyRef), + catchError((error) => this.handleError(error)), + switchMap(() => this.router.navigateByUrl('/entries')) + ) + .subscribe(); } - updateEntry() { - // this.api - // .updateWeightEntry( - // this.entryId!, - // this.getWeighedAt(), - // this.formGroup.controls['weight'].value, - // this.formGroup.controls['comment'].value, - // this.formGroup.controls['rabbit'].value!.id, - // this.formGroup.controls['medicines'].value?.map((x) => x.id) ?? [], - // ) - // .subscribe({ - // next: (_) => { - // this.router.navigateByUrl('/weight-entries'); - // }, - // error: (error: HttpErrorResponse) => { - // switch (true) { - // case error.status >= 500 && error.status <= 599: - // this.messageService.add({ - // severity: 'error', - // summary: 'Serverfehler', - // detail: - // 'Beim Aktualisieren des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.', - // }); - // break; - // case error.status == 400: - // this.messageService.add({ - // severity: 'error', - // summary: 'Clientfehler', - // detail: - // 'Die Anwendung scheint falsche Daten an den Server zu senden.', - // }); - // break; - // default: - // break; - // } - // }, - // }); + updateEntry(id: string) { + var request: UpdateConsumptionEntry = this.getFormData(); + this.consumptionClient.update(id, request) + .pipe( + takeUntilDestroyed(this.destroyRef), + catchError((error) => this.handleError(error)), + switchMap(() => this.router.navigateByUrl('/entries')) + ) + .subscribe(); + } + + private handleError(error: unknown): Observable { + if (!(error instanceof HttpErrorResponse)) { + return throwError(() => error); + } + + switch (true) { + case error.status >= 500 && error.status <= 599: + this.messageService.add({ + severity: 'error', + summary: 'Serverfehler', + detail: + 'Beim Erstellen des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.', + }); + break; + case error.status == 400: + this.messageService.add({ + severity: 'error', + summary: 'Clientfehler', + detail: + 'Die Anwendung scheint falsche Daten an den Server zu senden.', + }); + break; + default: + console.error(error); + this.messageService.add({ + severity: 'error', + summary: 'Unerwarteter Fehler', + detail: + 'Beim Erstellen des Eintrags hat der Server eine unerwartete Antwort zurückgegeben.', + }); + break; + } + + return EMPTY; } } -- 2.49.1 From d8f82bb2d197bb03774ef3facd19227d18222ac1 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 13:38:40 +0200 Subject: [PATCH 102/150] Add entry filtering --- .../entries/entries/entries.component.html | 4 +- .../entries/entries/entries.component.ts | 50 +++++++++++++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html index df3e664..5dd85e0 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html @@ -2,8 +2,8 @@
- +
diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index 2abd85d..bfbf62d 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -1,18 +1,22 @@ import { AsyncPipe, CommonModule } from '@angular/common'; import { Component, inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { ReactiveFormsModule } from '@angular/forms'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; import { CarClient } from '@vegasco-web/api/cars/car-client'; import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; +import { MessageService } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; import { DataViewModule } from 'primeng/dataview'; import { ScrollTopModule } from 'primeng/scrolltop'; import { SelectModule } from 'primeng/select'; import { SkeletonModule } from 'primeng/skeleton'; import { + BehaviorSubject, + combineLatest, map, Observable, + startWith, tap } from 'rxjs'; @@ -35,29 +39,57 @@ import { export class EntriesComponent { private readonly consumptionClient = inject(ConsumptionClient); private readonly carClient = inject(CarClient); + private readonly messageService = inject(MessageService); protected readonly consumptionEntries$: Observable; protected readonly cars$: Observable; protected readonly skeletonsIterationSource = Array(10).fill(0); + protected readonly selectedCar = new FormControl(null); + + private readonly deletedEntries$ = new BehaviorSubject([]); + constructor() { - this.consumptionEntries$ = this.consumptionClient.getAll() + const consumptionEntries = this.consumptionClient.getAll() + .pipe( + map(response => response.consumptions) + ); + + this.consumptionEntries$ = combineLatest([ + consumptionEntries, + this.selectedCar.valueChanges.pipe(startWith(null)), + this.deletedEntries$, + ]) .pipe( takeUntilDestroyed(), - tap((response) => { - console.log('Entries response:', response); - }), - map(response => response.consumptions) + map(([entries, selectedCar, deletedEntries]) => { + const nonDeletedEntries = + deletedEntries.length === 0 + ? entries + : entries.filter(entry => !deletedEntries.includes(entry.id)); + + if (!selectedCar) { + return nonDeletedEntries; + } + + return nonDeletedEntries.filter(entry => entry.carId === selectedCar.id); + }) ); this.cars$ = this.carClient.getAll() .pipe( takeUntilDestroyed(), - tap((response) => { - console.log('Cars response:', response); - }), map(response => response.cars) ); } + + onEntryDeleted(entry: ConsumptionEntry): void { + this.deletedEntries$.next([...this.deletedEntries$.value, entry.id]); + this.messageService.add({ + severity: 'success', + summary: 'Eintrag gelöscht', + detail: 'Der Eintrag wurde erfolgreich gelöscht.', + }); + } } -- 2.49.1 From c5555b30031e1cad919a6ddf0ad7aee57ff04148 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 14:40:14 +0200 Subject: [PATCH 103/150] Finish implementing editing and displaying entries --- src/Vegasco-Web/package.json | 3 + src/Vegasco-Web/pnpm-lock.yaml | 37 +++++ .../src/app/api/models/consumption.ts | 9 ++ .../edit-entry/edit-entry.component.ts | 20 +-- .../entry-card/entry-card.component.html | 32 +++++ .../entry-card/entry-card.component.scss | 3 + .../entry-card/entry-card.component.ts | 133 ++++++++++++++++++ .../entries/entries/entries.component.html | 5 +- .../entries/entries/entries.component.ts | 29 ++-- .../services/entries-overview.service.ts | 49 +++++++ .../src/app/services/routing.service.ts | 21 +++ 11 files changed, 312 insertions(+), 29 deletions(-) create mode 100644 src/Vegasco-Web/src/app/api/models/consumption.ts create mode 100644 src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.html create mode 100644 src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.scss create mode 100644 src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts create mode 100644 src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts create mode 100644 src/Vegasco-Web/src/app/services/routing.service.ts diff --git a/src/Vegasco-Web/package.json b/src/Vegasco-Web/package.json index f7ad866..14d4723 100644 --- a/src/Vegasco-Web/package.json +++ b/src/Vegasco-Web/package.json @@ -18,6 +18,9 @@ "@angular/forms": "^19.2.14", "@angular/platform-browser": "^19.2.14", "@angular/router": "^19.2.14", + "@ng-icons/core": "^31.4.0", + "@ng-icons/material-file-icons": "^31.4.0", + "@ng-icons/material-icons": "^31.4.0", "@primeng/themes": "^19.1.3", "@tailwindcss/postcss": "^4.1.10", "keycloak-angular": "^19.0.2", diff --git a/src/Vegasco-Web/pnpm-lock.yaml b/src/Vegasco-Web/pnpm-lock.yaml index e107ca7..4d19323 100644 --- a/src/Vegasco-Web/pnpm-lock.yaml +++ b/src/Vegasco-Web/pnpm-lock.yaml @@ -26,6 +26,15 @@ importers: '@angular/router': specifier: ^19.2.14 version: 19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2) + '@ng-icons/core': + specifier: ^31.4.0 + version: 31.4.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@ng-icons/material-file-icons': + specifier: ^31.4.0 + version: 31.4.0 + '@ng-icons/material-icons': + specifier: ^31.4.0 + version: 31.4.0 '@primeng/themes': specifier: ^19.1.3 version: 19.1.3 @@ -1305,6 +1314,19 @@ packages: resolution: {integrity: sha512-zM0mVWSXE0a0h9aKACLwKmD6nHcRiKrPpCfvaKqG1CqDEyjEawId0ocXxVzPMCAm6kkWr2P025msfxXEnt8UGQ==} engines: {node: '>= 10'} + '@ng-icons/core@31.4.0': + resolution: {integrity: sha512-JfLiJGDX/ihWmawcnLGXtwyCqMi2qXz7gMJyXXWdUN5JA18EAnt3JnyuxDAGkoU/u7wRlcOI7irlXHU4spAKOg==} + peerDependencies: + '@angular/common': '>=18.0.0' + '@angular/core': '>=18.0.0' + rxjs: ^6.5.3 || ^7.4.0 + + '@ng-icons/material-file-icons@31.4.0': + resolution: {integrity: sha512-Ffh61ghuuDRxelfTe/rHQ5IFCqUget/JeZ/NLq6QWLBycxUC6PjiEIIAXQvnVmYwCHNgxjBIRExP1/+vdHriNQ==} + + '@ng-icons/material-icons@31.4.0': + resolution: {integrity: sha512-JCxwM0LXwOgT5LD99p5TwPM6dPQ5x1BGieNzAstz7vk5+aiASg3fqs3rjNx7CbN3c2QjJ8+KuKrCCBzT9DCkOQ==} + '@ngtools/webpack@19.2.15': resolution: {integrity: sha512-H37nop/wWMkSgoU2VvrMzanHePdLRRrX52nC5tT2ZhH3qP25+PrnMyw11PoLDLv3iWXC68uB1AiKNIT+jiQbuQ==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -5797,6 +5819,21 @@ snapshots: '@napi-rs/nice-win32-x64-msvc': 1.0.1 optional: true + '@ng-icons/core@31.4.0(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2)': + dependencies: + '@angular/common': 19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2) + '@angular/core': 19.2.14(rxjs@7.8.2)(zone.js@0.15.1) + rxjs: 7.8.2 + tslib: 2.8.1 + + '@ng-icons/material-file-icons@31.4.0': + dependencies: + tslib: 2.8.1 + + '@ng-icons/material-icons@31.4.0': + dependencies: + tslib: 2.8.1 + '@ngtools/webpack@19.2.15(@angular/compiler-cli@19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(esbuild@0.25.4))': dependencies: '@angular/compiler-cli': 19.2.14(@angular/compiler@19.2.14)(typescript@5.8.3) diff --git a/src/Vegasco-Web/src/app/api/models/consumption.ts b/src/Vegasco-Web/src/app/api/models/consumption.ts new file mode 100644 index 0000000..0462c83 --- /dev/null +++ b/src/Vegasco-Web/src/app/api/models/consumption.ts @@ -0,0 +1,9 @@ +export interface Consumption { + id: string; + dateTime: string; + distance: number; + amount: number; + ignoreInCalculation: boolean; + carId: string; + car: Car; +} \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts index a0e0b68..d957172 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts @@ -1,8 +1,11 @@ +import { HttpErrorResponse } from '@angular/common/http'; import { Component, computed, DestroyRef, inject, input, Signal } from '@angular/core'; import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; -import { Router } from '@angular/router'; import { CarClient } from '@vegasco-web/api/cars/car-client'; +import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; +import { RoutingService } from '@vegasco-web/services/routing.service'; +import { MessageService } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; import { ChipModule } from 'primeng/chip'; import { DatePickerModule } from 'primeng/datepicker'; @@ -14,11 +17,8 @@ import { InputTextModule } from 'primeng/inputtext'; import { MultiSelectModule } from 'primeng/multiselect'; import { SelectModule } from 'primeng/select'; import { SkeletonModule } from 'primeng/skeleton'; -import { catchError, EMPTY, map, Observable, switchMap, tap, throwError } from 'rxjs'; +import { catchError, EMPTY, map, Observable, switchMap, throwError } from 'rxjs'; import { RequiredMarkerComponent } from './components/required-marker.component'; -import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; -import { HttpErrorResponse } from '@angular/common/http'; -import { MessageService } from 'primeng/api'; @Component({ selector: 'app-edit-entry', @@ -44,7 +44,7 @@ export class EditEntryComponent { private readonly formBuilder = inject(FormBuilder); private readonly carClient = inject(CarClient); private readonly consumptionClient = inject(ConsumptionClient); - private readonly router = inject(Router); + private readonly routingService = inject(RoutingService); private readonly destroyRef = inject(DestroyRef); private readonly messageService = inject(MessageService); @@ -88,7 +88,7 @@ export class EditEntryComponent { } async navigateToOverviewPage(): Promise { - await this.router.navigateByUrl(`/entries`); + await this.routingService.navigateToEntries(); } onSubmit(): void { @@ -119,7 +119,7 @@ export class EditEntryComponent { .pipe( takeUntilDestroyed(this.destroyRef), catchError((error) => this.handleError(error)), - switchMap(() => this.router.navigateByUrl('/entries')) + switchMap(() => this.routingService.navigateToEntries()) ) .subscribe(); } @@ -130,7 +130,7 @@ export class EditEntryComponent { .pipe( takeUntilDestroyed(this.destroyRef), catchError((error) => this.handleError(error)), - switchMap(() => this.router.navigateByUrl('/entries')) + switchMap(() => this.routingService.navigateToEntries()) ) .subscribe(); } @@ -149,7 +149,7 @@ export class EditEntryComponent { 'Beim Erstellen des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.', }); break; - case error.status == 400: + case error.status === 400: this.messageService.add({ severity: 'error', summary: 'Clientfehler', diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.html new file mode 100644 index 0000000..95f36be --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.html @@ -0,0 +1,32 @@ + + +
+
+
+ +
+
{{ entry().dateTime | date:"dd.MM.yyyy" }}
+
+ +
+
{{ entry().car.name }}
+
+ +
+ {{entry().distance }} km +
+ +
+ {{entry().amount }} ℓ +
+
+
+
+ + +
+
\ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.scss b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.scss new file mode 100644 index 0000000..2006a40 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.scss @@ -0,0 +1,3 @@ +.edit-button { + cursor: pointer; +} \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts new file mode 100644 index 0000000..2e7988f --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts @@ -0,0 +1,133 @@ +import { DatePipe } from '@angular/common'; +import { Component, DestroyRef, inject, input, output } from '@angular/core'; +import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; +import { Consumption } from '@vegasco-web/api/models/consumption'; +import { RoutingService } from '@vegasco-web/services/routing.service'; +import { ConfirmationService, MessageService } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; +import { CardModule } from 'primeng/card'; +import { ConfirmDialogModule } from 'primeng/confirmdialog'; +import { TooltipModule } from 'primeng/tooltip'; +import { + matCalendarMonthSharp, + matCommentSharp, + matDeleteSharp, + matMedicationSharp, + matPetsSharp, + matScaleSharp, +} from '@ng-icons/material-icons/sharp'; +import { NgIconComponent, provideIcons } from '@ng-icons/core'; +import { HttpErrorResponse } from '@angular/common/http'; +import { catchError, EMPTY, Observable, takeUntil, tap, throwError } from 'rxjs'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; + + +@Component({ + selector: 'app-entry-card', + imports: [ + DatePipe, + TooltipModule, + ButtonModule, + CardModule, + ConfirmDialogModule, + NgIconComponent, + ], + providers: [ + provideIcons({ + matDeleteSharp, + }), + ConfirmationService, + ], + templateUrl: './entry-card.component.html', + styleUrl: './entry-card.component.scss' +}) +export class EntryCardComponent { + readonly entry = input.required(); + + readonly entryDeleted = output(); + + private readonly routingService = inject(RoutingService); + private readonly consumptionClient = inject(ConsumptionClient); + private readonly messageService = inject(MessageService); + private readonly confirmationService = inject(ConfirmationService); + + private readonly destroyRef = inject(DestroyRef); + + async navigateToEdit(): Promise { + await this.routingService.navigateToEditEntry(this.entry().id); + } + + confirmDeleteEntry(): void { + const weighedAt = new Date( + Date.parse(this.entry().dateTime), + ).toLocaleString('de-DE', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + }); + + this.confirmationService.confirm({ + closeOnEscape: true, + dismissableMask: true, + header: 'Bist du sicher?', + message: `Möchtest du diesen Eintrag (${weighedAt} für ${this.entry().car.name}) wirklich löschen?`, + acceptButtonProps: { + label: 'Löschen', + severity: 'danger', + }, + rejectButtonProps: { + label: 'Abbrechen', + outlined: true, + }, + accept: () => this.deleteEntry(), + }); + } + + deleteEntry(): void { + this.consumptionClient.delete(this.entry().id) + .pipe( + takeUntilDestroyed(this.destroyRef), + tap(() => this.entryDeleted.emit(this.entry())), + catchError((error) => this.handleError(error)), + ) + .subscribe(); + } + + private handleError(error: unknown): Observable { + if (!(error instanceof HttpErrorResponse)) { + return throwError(() => error); + } + + switch (true) { + case error.status >= 500 && error.status <= 599: + this.messageService.add({ + severity: 'error', + summary: 'Serverfehler', + detail: + 'Beim Erstellen des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.', + }); + break; + case error.status === 400: + this.messageService.add({ + severity: 'error', + summary: 'Clientfehler', + detail: + 'Die Anwendung scheint falsche Daten an den Server zu senden.', + }); + break; + default: + console.error(error); + this.messageService.add({ + severity: 'error', + summary: 'Unerwarteter Fehler', + detail: + 'Beim Erstellen des Eintrags hat der Server eine unerwartete Antwort zurückgegeben.', + }); + break; + } + + return EMPTY; + } +} diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html index 5dd85e0..ec43a1c 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html @@ -26,9 +26,8 @@
@for (entry of entries; track entry.id) { - {{ entry | json }} - + }
diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index bfbf62d..27b6d74 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -3,8 +3,6 @@ import { Component, inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; -import { CarClient } from '@vegasco-web/api/cars/car-client'; -import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; import { MessageService } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; import { DataViewModule } from 'primeng/dataview'; @@ -16,9 +14,10 @@ import { combineLatest, map, Observable, - startWith, - tap + startWith } from 'rxjs'; +import { EntriesOverviewService } from './services/entries-overview.service'; +import { EntryCardComponent } from './components/entry-card/entry-card.component'; @Component({ selector: 'app-entries', @@ -27,18 +26,21 @@ import { ButtonModule, CommonModule, DataViewModule, + EntryCardComponent, SkeletonModule, SelectModule, ReactiveFormsModule, RouterLink, ScrollTopModule, ], + providers: [ + EntriesOverviewService, + ], templateUrl: './entries.component.html', styleUrl: './entries.component.scss' }) export class EntriesComponent { - private readonly consumptionClient = inject(ConsumptionClient); - private readonly carClient = inject(CarClient); + private readonly entriesOverviewService = inject(EntriesOverviewService); private readonly messageService = inject(MessageService); protected readonly consumptionEntries$: Observable; @@ -51,13 +53,11 @@ export class EntriesComponent { private readonly deletedEntries$ = new BehaviorSubject([]); constructor() { - const consumptionEntries = this.consumptionClient.getAll() - .pipe( - map(response => response.consumptions) - ); + const entries = this.entriesOverviewService.getEntries() + .pipe(takeUntilDestroyed()); this.consumptionEntries$ = combineLatest([ - consumptionEntries, + entries, this.selectedCar.valueChanges.pipe(startWith(null)), this.deletedEntries$, ]) @@ -77,11 +77,8 @@ export class EntriesComponent { }) ); - this.cars$ = this.carClient.getAll() - .pipe( - takeUntilDestroyed(), - map(response => response.cars) - ); + this.cars$ = this.entriesOverviewService.getCars() + .pipe(takeUntilDestroyed()); } onEntryDeleted(entry: ConsumptionEntry): void { diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts b/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts new file mode 100644 index 0000000..1483dd4 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts @@ -0,0 +1,49 @@ +import { inject, Injectable } from "@angular/core"; +import { CarClient } from "@vegasco-web/api/cars/car-client"; +import { ConsumptionClient } from "@vegasco-web/api/consumptions/consumption-client"; +import { Consumption } from "@vegasco-web/api/models/consumption"; +import { RoutingService } from "@vegasco-web/services/routing.service"; +import { combineLatest, map, Observable, shareReplay } from "rxjs"; + +@Injectable() +export class EntriesOverviewService { + private readonly carClient = inject(CarClient); + private readonly consumptionClient = inject(ConsumptionClient); + private readonly routingService = inject(RoutingService); + + private cachedCars$: Observable | null = null; + + private ensureCarsAreCached(): void { + if (this.cachedCars$ !== null) { + return; + } + + this.cachedCars$ = this.carClient.getAll() + .pipe( + map(response => response.cars), + shareReplay(1) + ); + } + + getEntries(): Observable { + this.ensureCarsAreCached(); + + const entries$ = this.consumptionClient.getAll() + .pipe(map(response => response.consumptions)); + + return combineLatest([this.cachedCars$!, entries$]) + .pipe( + map(([cars, entries]) => { + return entries.map((entry): Consumption => ({ + ...entry, + car: cars.find(car => car.id === entry.carId)! + })); + }) + ) + } + + getCars(): Observable { + this.ensureCarsAreCached(); + return this.cachedCars$!; + } +} \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/services/routing.service.ts b/src/Vegasco-Web/src/app/services/routing.service.ts new file mode 100644 index 0000000..2c4ae34 --- /dev/null +++ b/src/Vegasco-Web/src/app/services/routing.service.ts @@ -0,0 +1,21 @@ +import { inject, Injectable } from "@angular/core"; +import { Router } from "@angular/router"; + +@Injectable({ + providedIn: 'root' +}) +export class RoutingService { + private readonly router = inject(Router); + + async navigateToEntries(): Promise { + await this.router.navigateByUrl('/entries'); + } + + async navigateToEditEntry(entryId: string): Promise { + await this.router.navigate(['entries', 'edit', entryId]); + } + + async navigateToCreateEntry(): Promise { + await this.router.navigate(['entries', 'create']); + } +} \ No newline at end of file -- 2.49.1 From 87d81f98e9d3efef48c3e51aed7fc7279f5f0559 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 14:49:27 +0200 Subject: [PATCH 104/150] Remove duplicate import and switch to css @use --- src/Vegasco-Web/src/styles.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Vegasco-Web/src/styles.scss b/src/Vegasco-Web/src/styles.scss index 18fb1cc..8064e90 100644 --- a/src/Vegasco-Web/src/styles.scss +++ b/src/Vegasco-Web/src/styles.scss @@ -1,5 +1,4 @@ -@import "tailwindcss"; -@import "tailwindcss"; +@use "tailwindcss"; @plugin "tailwindcss-primeui"; html, -- 2.49.1 From b6f9b5fb26b17327baa7bfb13a7984cf51ea50aa Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 15:04:09 +0200 Subject: [PATCH 105/150] Remove unnecessary db roundtrip when deleting an entry --- src/Vegasco.Server.Api/Cars/DeleteCar.cs | 20 +++++++++------- .../Consumptions/DeleteConsumptions.cs | 23 +++++++++++-------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/Vegasco.Server.Api/Cars/DeleteCar.cs b/src/Vegasco.Server.Api/Cars/DeleteCar.cs index 271b1a2..a86fb04 100644 --- a/src/Vegasco.Server.Api/Cars/DeleteCar.cs +++ b/src/Vegasco.Server.Api/Cars/DeleteCar.cs @@ -1,4 +1,5 @@ -using Vegasco.Server.Api.Persistence; +using Microsoft.EntityFrameworkCore; +using Vegasco.Server.Api.Persistence; namespace Vegasco.Server.Api.Cars; @@ -17,18 +18,21 @@ public static class DeleteCar public static async Task Endpoint( Guid id, ApplicationDbContext dbContext, + ILoggerFactory loggerFactory, CancellationToken cancellationToken) { - Car? car = await dbContext.Cars.FindAsync([new CarId(id)], cancellationToken: cancellationToken); + var rows = await dbContext.Cars + .Where(x => x.Id == new CarId(id)) + .ExecuteDeleteAsync(cancellationToken); - if (car is null) + if (rows > 1) { - return TypedResults.NotFound(); + var logger = loggerFactory.CreateLogger(nameof(DeleteCar)); + logger.LogWarning("Deleted '{DeletedRowCount}' rows for id '{CarId}'", rows, id); } - dbContext.Cars.Remove(car); - await dbContext.SaveChangesAsync(cancellationToken); - - return TypedResults.NoContent(); + return rows > 0 + ? TypedResults.NoContent() + : TypedResults.NotFound(); } } \ No newline at end of file diff --git a/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs index bedc45c..1cbcb46 100644 --- a/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs @@ -1,4 +1,5 @@ -using Vegasco.Server.Api.Persistence; +using Microsoft.EntityFrameworkCore; +using Vegasco.Server.Api.Persistence; namespace Vegasco.Server.Api.Consumptions; @@ -15,19 +16,23 @@ public static class DeleteConsumption } private static async Task Endpoint( - ApplicationDbContext dbContext, Guid id, + ApplicationDbContext dbContext, + ILoggerFactory loggerFactory, CancellationToken cancellationToken) { - Consumption? consumption = await dbContext.Consumptions.FindAsync([new ConsumptionId(id)], cancellationToken); - if (consumption is null) + var rows = await dbContext.Consumptions + .Where(x => x.Id == new ConsumptionId(id)) + .ExecuteDeleteAsync(cancellationToken); + + if (rows > 1) { - return TypedResults.NotFound(); + var logger = loggerFactory.CreateLogger(nameof(DeleteConsumption)); + logger.LogWarning("Deleted '{DeletedRowCount}' rows for id '{ConsumptionId}'", rows, id); } - dbContext.Consumptions.Remove(consumption); - await dbContext.SaveChangesAsync(cancellationToken); - - return TypedResults.NoContent(); + return rows > 0 + ? TypedResults.NoContent() + : TypedResults.NotFound(); } } \ No newline at end of file -- 2.49.1 From fd9b9c7c2ed544d8b264f13b541cb11e290455d7 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 15:04:27 +0200 Subject: [PATCH 106/150] Fix copied texts --- .../entries/components/entry-card/entry-card.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts index 2e7988f..7ccba4c 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts @@ -106,7 +106,7 @@ export class EntryCardComponent { severity: 'error', summary: 'Serverfehler', detail: - 'Beim Erstellen des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.', + 'Beim Löschen des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.', }); break; case error.status === 400: @@ -123,7 +123,7 @@ export class EntryCardComponent { severity: 'error', summary: 'Unerwarteter Fehler', detail: - 'Beim Erstellen des Eintrags hat der Server eine unerwartete Antwort zurückgegeben.', + 'Beim Löschen des Eintrags hat der Server eine unerwartete Antwort zurückgegeben.', }); break; } -- 2.49.1 From b07b0c1f0f7896691e5c04ac398744d3b36256e1 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 17:05:19 +0200 Subject: [PATCH 107/150] Make code more understandable --- src/Vegasco.Server.Api/Cars/DeleteCar.cs | 9 ++++++--- .../Consumptions/DeleteConsumptions.cs | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Vegasco.Server.Api/Cars/DeleteCar.cs b/src/Vegasco.Server.Api/Cars/DeleteCar.cs index a86fb04..a6d0aab 100644 --- a/src/Vegasco.Server.Api/Cars/DeleteCar.cs +++ b/src/Vegasco.Server.Api/Cars/DeleteCar.cs @@ -25,14 +25,17 @@ public static class DeleteCar .Where(x => x.Id == new CarId(id)) .ExecuteDeleteAsync(cancellationToken); + if (rows == 0) + { + return TypedResults.NotFound(); + } + if (rows > 1) { var logger = loggerFactory.CreateLogger(nameof(DeleteCar)); logger.LogWarning("Deleted '{DeletedRowCount}' rows for id '{CarId}'", rows, id); } - return rows > 0 - ? TypedResults.NoContent() - : TypedResults.NotFound(); + return TypedResults.NoContent(); } } \ No newline at end of file diff --git a/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs index 1cbcb46..774741b 100644 --- a/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs @@ -25,14 +25,17 @@ public static class DeleteConsumption .Where(x => x.Id == new ConsumptionId(id)) .ExecuteDeleteAsync(cancellationToken); + if (rows == 0) + { + return TypedResults.NotFound(); + } + if (rows > 1) { var logger = loggerFactory.CreateLogger(nameof(DeleteConsumption)); logger.LogWarning("Deleted '{DeletedRowCount}' rows for id '{ConsumptionId}'", rows, id); } - return rows > 0 - ? TypedResults.NoContent() - : TypedResults.NotFound(); + return TypedResults.NoContent(); } } \ No newline at end of file -- 2.49.1 From b9375d66b65644c8b687c1576de829ef6261a5f9 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 17:11:11 +0200 Subject: [PATCH 108/150] Update imports --- .../entry-card/entry-card.component.ts | 23 +++++++------------ .../entries/entries/entries.component.html | 2 +- .../entries/entries/entries.component.ts | 12 ++++++++-- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts index 7ccba4c..57c2b47 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts @@ -1,5 +1,11 @@ import { DatePipe } from '@angular/common'; +import { HttpErrorResponse } from '@angular/common/http'; import { Component, DestroyRef, inject, input, output } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { NgIconComponent, provideIcons } from '@ng-icons/core'; +import { + matDeleteSharp +} from '@ng-icons/material-icons/sharp'; import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; import { Consumption } from '@vegasco-web/api/models/consumption'; import { RoutingService } from '@vegasco-web/services/routing.service'; @@ -7,29 +13,16 @@ import { ConfirmationService, MessageService } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; import { CardModule } from 'primeng/card'; import { ConfirmDialogModule } from 'primeng/confirmdialog'; -import { TooltipModule } from 'primeng/tooltip'; -import { - matCalendarMonthSharp, - matCommentSharp, - matDeleteSharp, - matMedicationSharp, - matPetsSharp, - matScaleSharp, -} from '@ng-icons/material-icons/sharp'; -import { NgIconComponent, provideIcons } from '@ng-icons/core'; -import { HttpErrorResponse } from '@angular/common/http'; -import { catchError, EMPTY, Observable, takeUntil, tap, throwError } from 'rxjs'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { catchError, EMPTY, Observable, tap, throwError } from 'rxjs'; @Component({ selector: 'app-entry-card', imports: [ - DatePipe, - TooltipModule, ButtonModule, CardModule, ConfirmDialogModule, + DatePipe, NgIconComponent, ], providers: [ diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html index ec43a1c..2fed206 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html @@ -7,7 +7,7 @@
- +
diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index 27b6d74..50eb01e 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -18,6 +18,10 @@ import { } from 'rxjs'; import { EntriesOverviewService } from './services/entries-overview.service'; import { EntryCardComponent } from './components/entry-card/entry-card.component'; +import { + matAddSharp +} from '@ng-icons/material-icons/sharp'; +import { NgIconComponent, provideIcons } from '@ng-icons/core'; @Component({ selector: 'app-entries', @@ -27,13 +31,17 @@ import { EntryCardComponent } from './components/entry-card/entry-card.component CommonModule, DataViewModule, EntryCardComponent, - SkeletonModule, - SelectModule, + NgIconComponent, ReactiveFormsModule, RouterLink, ScrollTopModule, + SelectModule, + SkeletonModule, ], providers: [ + provideIcons({ + matAddSharp, + }), EntriesOverviewService, ], templateUrl: './entries.component.html', -- 2.49.1 From 5978a96dd7a71e36e6090ebae553b6b14efea79e Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 17:42:08 +0200 Subject: [PATCH 109/150] Make date required and set today as default --- .../modules/entries/edit-entry/edit-entry.component.html | 9 ++++++--- .../modules/entries/edit-entry/edit-entry.component.ts | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html index 0c5f2a7..1ee6f31 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html @@ -20,9 +20,12 @@
- +
+ +
null, Validators.required], - [this.formFieldNames.date]: [null], + [this.formFieldNames.date]: [new Date(), Validators.required], [this.formFieldNames.mileage]: [ null, [Validators.required, Validators.min(1)], -- 2.49.1 From 92e4da4b9323f3a8c1015a718939540a5bff6b35 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 17:42:14 +0200 Subject: [PATCH 110/150] Add icons in card --- .../entry-card/entry-card.component.html | 20 +++++++++++++++---- .../entry-card/entry-card.component.ts | 12 ++++++++++- .../entries/entries/entries.component.ts | 2 +- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.html index 95f36be..0140a69 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.html @@ -6,19 +6,31 @@
-
{{ entry().dateTime | date:"dd.MM.yyyy" }}
+
+ +
{{ entry().dateTime | date:"dd.MM.yyyy" }}
+
-
{{ entry().car.name }}
+
+ +
{{ entry().car.name }}
+
- {{entry().distance }} km +
+ +
{{entry().distance }} km
+
- {{entry().amount }} ℓ +
+ +
{{entry().amount }} ℓ
+
diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts index 57c2b47..b8f2366 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts @@ -4,8 +4,14 @@ import { Component, DestroyRef, inject, input, output } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; import { - matDeleteSharp + matCalendarMonthSharp, + matDeleteSharp, + matStraightenSharp, + matLocalGasStationSharp, } from '@ng-icons/material-icons/sharp'; +import { + matDirectionsCarOutline, +} from '@ng-icons/material-icons/outline'; import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; import { Consumption } from '@vegasco-web/api/models/consumption'; import { RoutingService } from '@vegasco-web/services/routing.service'; @@ -28,6 +34,10 @@ import { catchError, EMPTY, Observable, tap, throwError } from 'rxjs'; providers: [ provideIcons({ matDeleteSharp, + matCalendarMonthSharp, + matDirectionsCarOutline, + matStraightenSharp, + matLocalGasStationSharp, }), ConfirmationService, ], diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index 50eb01e..803e9e5 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -19,7 +19,7 @@ import { import { EntriesOverviewService } from './services/entries-overview.service'; import { EntryCardComponent } from './components/entry-card/entry-card.component'; import { - matAddSharp + matAddSharp, } from '@ng-icons/material-icons/sharp'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; -- 2.49.1 From 2e3000c3fcaf2628e30399fc719b19516713d465 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 18:49:04 +0200 Subject: [PATCH 111/150] Add loading entry data when updating an entry --- .../edit-entry/edit-entry.component.ts | 137 ++++++++++++++---- 1 file changed, 106 insertions(+), 31 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts index 6e0a5c4..b36771b 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts @@ -1,7 +1,7 @@ import { HttpErrorResponse } from '@angular/common/http'; -import { Component, computed, DestroyRef, inject, input, Signal } from '@angular/core'; +import { Component, computed, DestroyRef, inject, input, OnInit, signal, Signal } from '@angular/core'; import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; -import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { CarClient } from '@vegasco-web/api/cars/car-client'; import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; import { RoutingService } from '@vegasco-web/services/routing.service'; @@ -17,7 +17,7 @@ import { InputTextModule } from 'primeng/inputtext'; import { MultiSelectModule } from 'primeng/multiselect'; import { SelectModule } from 'primeng/select'; import { SkeletonModule } from 'primeng/skeleton'; -import { catchError, EMPTY, map, Observable, switchMap, throwError } from 'rxjs'; +import { catchError, combineLatest, EMPTY, filter, map, Observable, switchMap, tap, throwError } from 'rxjs'; import { RequiredMarkerComponent } from './components/required-marker.component'; @Component({ @@ -40,15 +40,14 @@ import { RequiredMarkerComponent } from './components/required-marker.component' templateUrl: './edit-entry.component.html', styleUrl: './edit-entry.component.scss' }) -export class EditEntryComponent { - private readonly formBuilder = inject(FormBuilder); +export class EditEntryComponent implements OnInit { private readonly carClient = inject(CarClient); private readonly consumptionClient = inject(ConsumptionClient); private readonly routingService = inject(RoutingService); private readonly destroyRef = inject(DestroyRef); private readonly messageService = inject(MessageService); - protected readonly entryId = input(undefined); + protected readonly id = input(undefined); protected readonly formFieldNames = { car: 'car', @@ -57,34 +56,82 @@ export class EditEntryComponent { amount: 'amount', } as const; - protected readonly formGroup = this.formBuilder.group({ - [this.formFieldNames.car]: [null, Validators.required], - [this.formFieldNames.date]: [new Date(), Validators.required], - [this.formFieldNames.mileage]: [ - null, - [Validators.required, Validators.min(1)], - ], - [this.formFieldNames.amount]: [ - null, - [Validators.required, Validators.min(1)], - ], + protected readonly formGroup = new FormGroup({ + [this.formFieldNames.car]: new FormControl({ value: null, disabled: true }, [Validators.required]), + [this.formFieldNames.date]: new FormControl({ value: new Date(), disabled: true }, [Validators.required]), + [this.formFieldNames.mileage]: new FormControl({ value: null, disabled: true }, [Validators.required, Validators.min(1)]), + [this.formFieldNames.amount]: new FormControl({ value: null, disabled: true }, [Validators.required, Validators.min(1)]), }); + private readonly cars$: Observable; protected readonly cars: Signal; + private readonly isEntryDataLoaded = signal(false); + protected readonly isLoading = computed(() => { - return this.cars() === undefined; - }) + var cars = this.cars(); + var isEntryDataLoaded = this.isEntryDataLoaded(); + return cars === undefined || !isEntryDataLoaded; + }); constructor() { - this.cars = toSignal( - this.carClient - .getAll() - .pipe( - takeUntilDestroyed(), - map(response => response.cars) - ), - ); + this.cars$ = this.carClient + .getAll() + .pipe( + takeUntilDestroyed(), + map(response => response.cars) + ); + this.cars = toSignal(this.cars$); + } + + ngOnInit(): void { + this.loadEntryDetailsAndEnableControls(); + } + + private loadEntryDetailsAndEnableControls() { + const entryId = this.id(); + + if (entryId === undefined || entryId === null) { + this.enableFormControls(); + this.isEntryDataLoaded.set(true); + return; + } + + const consumption$ = this.consumptionClient + .getSingle(entryId); + + combineLatest([ + consumption$, this.cars$ + ]) + .pipe( + filter(([_, cars]) => cars !== undefined), + takeUntilDestroyed(this.destroyRef), + catchError((error) => this.handleGetError(error)), + tap(([consumption, cars]) => { + this.formGroup.patchValue({ + [this.formFieldNames.car]: cars!.find(c => c.id === consumption.carId) ?? null, + [this.formFieldNames.date]: new Date(consumption.dateTime), + [this.formFieldNames.mileage]: consumption.distance, + [this.formFieldNames.amount]: consumption.amount, + }); + }), + tap(() => { + this.enableFormControls(); + this.isEntryDataLoaded.set(true); + }), + ) + .subscribe(); + } + + private enableFormControls(): void { + for (const controlName of Object.values(this.formFieldNames)) { + const control = this.formGroup.get(controlName); + if (control) { + control.enable(); + } else { + console.warn(`Form control '${controlName}' not found.`); + } + } } async navigateToOverviewPage(): Promise { @@ -92,7 +139,7 @@ export class EditEntryComponent { } onSubmit(): void { - var entryId = this.entryId(); + var entryId = this.id(); if (entryId === undefined || entryId === null) { this.createEntry(); return; @@ -118,7 +165,7 @@ export class EditEntryComponent { this.consumptionClient.create(request) .pipe( takeUntilDestroyed(this.destroyRef), - catchError((error) => this.handleError(error)), + catchError((error) => this.handleCreateOrUpdateError(error)), switchMap(() => this.routingService.navigateToEntries()) ) .subscribe(); @@ -129,13 +176,41 @@ export class EditEntryComponent { this.consumptionClient.update(id, request) .pipe( takeUntilDestroyed(this.destroyRef), - catchError((error) => this.handleError(error)), + catchError((error) => this.handleCreateOrUpdateError(error)), switchMap(() => this.routingService.navigateToEntries()) ) .subscribe(); } - private handleError(error: unknown): Observable { + private handleGetError(error: unknown): Observable { + if (!(error instanceof HttpErrorResponse)) { + return throwError(() => error); + } + + switch (true) { + case error.status >= 500 && error.status <= 599: + this.messageService.add({ + severity: 'error', + summary: 'Serverfehler', + detail: + 'Beim Erstellen des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.', + }); + break; + default: + console.error(error); + this.messageService.add({ + severity: 'error', + summary: 'Unerwarteter Fehler', + detail: + 'Beim Erstellen des Eintrags hat der Server eine unerwartete Antwort zurückgegeben.', + }); + break; + } + + return EMPTY; + } + + private handleCreateOrUpdateError(error: unknown): Observable { if (!(error instanceof HttpErrorResponse)) { return throwError(() => error); } -- 2.49.1 From 41c342bb0f8a7a71a52527ac77bc00410ff92775 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 18:56:24 +0200 Subject: [PATCH 112/150] Add more accurate loading skeletons --- src/Vegasco-Web/.vscode/launch.json | 2 +- .../entries/edit-entry/edit-entry.component.html | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Vegasco-Web/.vscode/launch.json b/src/Vegasco-Web/.vscode/launch.json index 80a5fe9..45307d0 100644 --- a/src/Vegasco-Web/.vscode/launch.json +++ b/src/Vegasco-Web/.vscode/launch.json @@ -3,7 +3,7 @@ "version": "0.2.0", "configurations": [ { - "name": "Launch (Chrome)", + "name": "Launch Web (Chrome)", "type": "chrome", "request": "launch", "preLaunchTask": "npm: start", diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html index 1ee6f31..55de4e4 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html @@ -1,5 +1,14 @@ @if (isLoading()) { - +
+ + + + +
+ + +
+
} @else { -- 2.49.1 From feadab4dff1f57e377c23af59dffd3835fa2980b Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 18:56:40 +0200 Subject: [PATCH 113/150] Sort entries both on the backend and frontend --- .../entries/services/entries-overview.service.ts | 10 ++++++---- src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs | 1 + 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts b/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts index 1483dd4..d3eb91e 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts @@ -34,10 +34,12 @@ export class EntriesOverviewService { return combineLatest([this.cachedCars$!, entries$]) .pipe( map(([cars, entries]) => { - return entries.map((entry): Consumption => ({ - ...entry, - car: cars.find(car => car.id === entry.carId)! - })); + return entries + .sort((a, b) => b.dateTime.localeCompare(a.dateTime)) + .map((entry): Consumption => ({ + ...entry, + car: cars.find(car => car.id === entry.carId)! + })); }) ) } diff --git a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs index 1d1c4c7..a472df0 100644 --- a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs @@ -41,6 +41,7 @@ public static class GetConsumptions CancellationToken cancellationToken) { List consumptions = await dbContext.Consumptions + .OrderByDescending(x => x.DateTime) .Select(x => new ResponseDto(x.Id.Value, x.DateTime, x.Distance, x.Amount, x.IgnoreInCalculation, x.CarId.Value)) .ToListAsync(cancellationToken); -- 2.49.1 From 1c8e02b3fad646239c31b258c541ecc35f702087 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 18:56:49 +0200 Subject: [PATCH 114/150] Add error handling --- .../entries/entries/entries.component.ts | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index 803e9e5..93de58e 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -11,10 +11,13 @@ import { SelectModule } from 'primeng/select'; import { SkeletonModule } from 'primeng/skeleton'; import { BehaviorSubject, + catchError, combineLatest, + EMPTY, map, Observable, - startWith + startWith, + throwError } from 'rxjs'; import { EntriesOverviewService } from './services/entries-overview.service'; import { EntryCardComponent } from './components/entry-card/entry-card.component'; @@ -22,6 +25,7 @@ import { matAddSharp, } from '@ng-icons/material-icons/sharp'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; +import { HttpErrorResponse } from '@angular/common/http'; @Component({ selector: 'app-entries', @@ -62,7 +66,10 @@ export class EntriesComponent { constructor() { const entries = this.entriesOverviewService.getEntries() - .pipe(takeUntilDestroyed()); + .pipe( + takeUntilDestroyed(), + catchError((error) => this.handleGetEntriesError(error)) + ); this.consumptionEntries$ = combineLatest([ entries, @@ -97,4 +104,32 @@ export class EntriesComponent { detail: 'Der Eintrag wurde erfolgreich gelöscht.', }); } + + private handleGetEntriesError(error: unknown): Observable { + if (!(error instanceof HttpErrorResponse)) { + return throwError(() => new Error('An unexpected error occurred')); + } + + switch (true) { + case error.status >= 500 && error.status <= 599: + this.messageService.add({ + severity: 'error', + summary: 'Serverfehler', + detail: + 'Beim Abrufen der Einträge ist ein Fehler aufgetreten. Bitte versuche es erneut.', + }); + break; + default: + console.error(error); + this.messageService.add({ + severity: 'error', + summary: 'Unerwarteter Fehler', + detail: + 'Beim Abrufen der Einträge hat der Server eine unerwartete Antwort zurückgegeben.', + }); + break; + } + + return EMPTY; + } } -- 2.49.1 From d71e523074683f1702a1e0fad92667671732685f Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 19:00:40 +0200 Subject: [PATCH 115/150] Terminate task after debug ends --- src/Vegasco-Web/.vscode/launch.json | 1 + src/Vegasco-Web/.vscode/tasks.json | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/src/Vegasco-Web/.vscode/launch.json b/src/Vegasco-Web/.vscode/launch.json index 45307d0..5d733e3 100644 --- a/src/Vegasco-Web/.vscode/launch.json +++ b/src/Vegasco-Web/.vscode/launch.json @@ -7,6 +7,7 @@ "type": "chrome", "request": "launch", "preLaunchTask": "npm: start", + "postDebugTask": "Terminate All Tasks", "url": "http://localhost:44200/", } ] diff --git a/src/Vegasco-Web/.vscode/tasks.json b/src/Vegasco-Web/.vscode/tasks.json index 91e9a1b..84052b9 100644 --- a/src/Vegasco-Web/.vscode/tasks.json +++ b/src/Vegasco-Web/.vscode/tasks.json @@ -2,6 +2,12 @@ // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 "version": "2.0.0", "tasks": [ + { + "label": "Terminate All Tasks", + "command": "echo ${input:terminate}", + "type": "shell", + "problemMatcher": [] + }, { "type": "npm", "script": "start", @@ -45,5 +51,13 @@ } } } + ], + "inputs": [ + { + "id": "terminate", + "type": "command", + "command": "workbench.action.tasks.terminate", + "args": "terminateAll" + } ] } -- 2.49.1 From f58613d6619c2ff21c934e28e0a9185a146f607e Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Thu, 19 Jun 2025 19:35:19 +0200 Subject: [PATCH 116/150] Remove unused variable --- .../modules/entries/entries/services/entries-overview.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts b/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts index d3eb91e..c026496 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts @@ -9,7 +9,6 @@ import { combineLatest, map, Observable, shareReplay } from "rxjs"; export class EntriesOverviewService { private readonly carClient = inject(CarClient); private readonly consumptionClient = inject(ConsumptionClient); - private readonly routingService = inject(RoutingService); private cachedCars$: Observable | null = null; -- 2.49.1 From 63c7624a003598ee990516074aee57ae6142db9a Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 20 Jun 2025 20:45:08 +0200 Subject: [PATCH 117/150] Persist and use selected car --- .../edit-entry/edit-entry.component.ts | 29 +++++++++++++- .../entries/entries/entries.component.ts | 38 +++++++++++++++++-- .../entries/services/selected-car.service.ts | 33 ++++++++++++++++ 3 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 src/Vegasco-Web/src/app/modules/entries/services/selected-car.service.ts diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts index b36771b..336d49b 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts @@ -19,6 +19,7 @@ import { SelectModule } from 'primeng/select'; import { SkeletonModule } from 'primeng/skeleton'; import { catchError, combineLatest, EMPTY, filter, map, Observable, switchMap, tap, throwError } from 'rxjs'; import { RequiredMarkerComponent } from './components/required-marker.component'; +import { SelectedCarService } from '../services/selected-car.service'; @Component({ selector: 'app-edit-entry', @@ -46,6 +47,7 @@ export class EditEntryComponent implements OnInit { private readonly routingService = inject(RoutingService); private readonly destroyRef = inject(DestroyRef); private readonly messageService = inject(MessageService); + private readonly selectedCarService = inject(SelectedCarService); protected readonly id = input(undefined); @@ -79,13 +81,38 @@ export class EditEntryComponent implements OnInit { .getAll() .pipe( takeUntilDestroyed(), - map(response => response.cars) + map(response => response.cars), + tap(cars => { + const selectedCarId = this.selectedCarService.getSelectedCarId(); + + if (selectedCarId === null) { + const firstCar = cars[0]; + this.formGroup.controls[this.formFieldNames.car].setValue(firstCar); + this.selectedCarService.setSelectedCarId(firstCar?.id ?? null); + return; + } + + const selectedCar = cars.find(car => car.id === selectedCarId); + this.formGroup.controls[this.formFieldNames.car].setValue(selectedCar ?? null); + this.selectedCarService.setSelectedCarId(selectedCar?.id ?? null); + }), ); this.cars = toSignal(this.cars$); } ngOnInit(): void { this.loadEntryDetailsAndEnableControls(); + + this.formGroup.controls[this.formFieldNames.car] + .valueChanges + .pipe( + takeUntilDestroyed(this.destroyRef), + tap((car) => { + console.log('Selected car changed (edit entry):', car); + this.selectedCarService.setSelectedCarId(car?.id ?? null); + }) + ) + .subscribe(); } private loadEntryDetailsAndEnableControls() { diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index 93de58e..fd7eace 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -1,5 +1,5 @@ import { AsyncPipe, CommonModule } from '@angular/common'; -import { Component, inject } from '@angular/core'; +import { Component, DestroyRef, inject, OnInit } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; @@ -17,6 +17,7 @@ import { map, Observable, startWith, + tap, throwError } from 'rxjs'; import { EntriesOverviewService } from './services/entries-overview.service'; @@ -26,6 +27,7 @@ import { } from '@ng-icons/material-icons/sharp'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; import { HttpErrorResponse } from '@angular/common/http'; +import { SelectedCarService } from '../services/selected-car.service'; @Component({ selector: 'app-entries', @@ -51,9 +53,11 @@ import { HttpErrorResponse } from '@angular/common/http'; templateUrl: './entries.component.html', styleUrl: './entries.component.scss' }) -export class EntriesComponent { +export class EntriesComponent implements OnInit { private readonly entriesOverviewService = inject(EntriesOverviewService); private readonly messageService = inject(MessageService); + private readonly selectedCarService = inject(SelectedCarService); + private readonly destroyRef = inject(DestroyRef); protected readonly consumptionEntries$: Observable; protected readonly cars$: Observable; @@ -93,7 +97,35 @@ export class EntriesComponent { ); this.cars$ = this.entriesOverviewService.getCars() - .pipe(takeUntilDestroyed()); + .pipe( + takeUntilDestroyed(), + tap((cars) => { + const selectedCarId = this.selectedCarService.getSelectedCarId(); + + if (selectedCarId === null) { + const firstCar = cars[0]; + this.selectedCar.setValue(firstCar); + this.selectedCarService.setSelectedCarId(firstCar?.id ?? null); + return; + } + + const selectedCar = cars.find(car => car.id === selectedCarId); + this.selectedCar.setValue(selectedCar ?? null); + this.selectedCarService.setSelectedCarId(selectedCar?.id ?? null); + }), + ); + } + + ngOnInit(): void { + this.selectedCar.valueChanges + .pipe( + takeUntilDestroyed(this.destroyRef), + tap((car) => { + console.log('Selected car changed (entries):', car); + this.selectedCarService.setSelectedCarId(car?.id ?? null); + }) + ) + .subscribe(); } onEntryDeleted(entry: ConsumptionEntry): void { diff --git a/src/Vegasco-Web/src/app/modules/entries/services/selected-car.service.ts b/src/Vegasco-Web/src/app/modules/entries/services/selected-car.service.ts new file mode 100644 index 0000000..e2c66d5 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/services/selected-car.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from "@angular/core"; +import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; +import { BehaviorSubject, tap } from "rxjs"; + +@Injectable({ + providedIn: "root", +}) +export class SelectedCarService { + static readonly SELECTED_CAR_ID_KEY = "SELECTED_CAR_ID"; + + private selectedCarId: string | null = null; + + constructor() { + this.loadStoredCarId(); + } + + private loadStoredCarId(): void { + this.selectedCarId = localStorage.getItem(SelectedCarService.SELECTED_CAR_ID_KEY); + } + + getSelectedCarId() { + return this.selectedCarId; + } + + setSelectedCarId(carId: string | null): void { + this.selectedCarId = carId; + if (carId === null) { + localStorage.removeItem(SelectedCarService.SELECTED_CAR_ID_KEY); + } else { + localStorage.setItem(SelectedCarService.SELECTED_CAR_ID_KEY, carId); + } + } +} \ No newline at end of file -- 2.49.1 From e13b5f2cdc2944f635a4b3feba75d42b9e28d938 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 20 Jun 2025 21:02:05 +0200 Subject: [PATCH 118/150] Remove log messages --- .../src/app/modules/entries/edit-entry/edit-entry.component.ts | 1 - .../src/app/modules/entries/entries/entries.component.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts index 336d49b..ce86a37 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts @@ -108,7 +108,6 @@ export class EditEntryComponent implements OnInit { .pipe( takeUntilDestroyed(this.destroyRef), tap((car) => { - console.log('Selected car changed (edit entry):', car); this.selectedCarService.setSelectedCarId(car?.id ?? null); }) ) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index fd7eace..aa38d73 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -121,7 +121,6 @@ export class EntriesComponent implements OnInit { .pipe( takeUntilDestroyed(this.destroyRef), tap((car) => { - console.log('Selected car changed (entries):', car); this.selectedCarService.setSelectedCarId(car?.id ?? null); }) ) -- 2.49.1 From 9246729edf64c250db3dfba35ae60ca6cef398cc Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 20 Jun 2025 21:02:38 +0200 Subject: [PATCH 119/150] Order cars by name --- .../src/app/modules/entries/edit-entry/edit-entry.component.ts | 3 ++- .../src/app/modules/entries/entries/entries.component.ts | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts index ce86a37..9fbf386 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts @@ -81,7 +81,8 @@ export class EditEntryComponent implements OnInit { .getAll() .pipe( takeUntilDestroyed(), - map(response => response.cars), + map(response => response.cars + .sort((a, b) => a.name.localeCompare(b.name))), tap(cars => { const selectedCarId = this.selectedCarService.getSelectedCarId(); diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index aa38d73..08e33e9 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -99,6 +99,8 @@ export class EntriesComponent implements OnInit { this.cars$ = this.entriesOverviewService.getCars() .pipe( takeUntilDestroyed(), + map((cars) => cars + .sort((a, b) => a.name.localeCompare(b.name))), tap((cars) => { const selectedCarId = this.selectedCarService.getSelectedCarId(); -- 2.49.1 From b8d1fddd91a67a406c73ff0ae83ee096676c9002 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 20 Jun 2025 21:03:47 +0200 Subject: [PATCH 120/150] Remove time portion No time is entered when creating / editing --- .../entries/components/entry-card/entry-card.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts index b8f2366..6945d4f 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts @@ -67,8 +67,6 @@ export class EntryCardComponent { year: 'numeric', month: '2-digit', day: '2-digit', - hour: '2-digit', - minute: '2-digit', }); this.confirmationService.confirm({ -- 2.49.1 From 8b9ccdc694b5bf5912b4bfe23f9f3c79a2ad6063 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 20 Jun 2025 21:05:54 +0200 Subject: [PATCH 121/150] Hide clear button for select which should always have a value --- .../src/app/modules/entries/entries/entries.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html index 2fed206..2a359cc 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.html @@ -2,7 +2,7 @@
-
-- 2.49.1 From 4c00f868c7f8f36f68a703cbf390a9e69c1b6868 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 20 Jun 2025 21:44:05 +0200 Subject: [PATCH 122/150] Update READMEs --- README.md | 6 ++++-- src/Vegasco-Web/README.md | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 54c564d..13a6fa5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Vegasco Server -Backend for the vegasco (**VE**hicle **GAS** **CO**nsumption) application. +Vegasco (**VE**hicle **GAS** **CO**nsumption) application. + +Includes the backend (`src/Vegasco.Server.Api`) and the frontend (`src/Vegasco-Web`). Utilizes [Aspire](https://learn.microsoft.com/en-us/dotnet/aspire/get-started/aspire-overview). ## Getting Started @@ -64,4 +66,4 @@ creates a Postgres database as a docker container, and starts the Api with the c Ensure you have an identity provider set up, for example Keycloak, and configured the relevant options described above. -Then, to run the application, ensure you have Docker running, then run the `Vegasco.Server.AppHost` launch profile. +Then, to run the application, ensure you have Docker running, then run either the `http` or `https` launch profile of the `Vegasco.Server.AppHost` project. diff --git a/src/Vegasco-Web/README.md b/src/Vegasco-Web/README.md index f4a28cc..412e7db 100644 --- a/src/Vegasco-Web/README.md +++ b/src/Vegasco-Web/README.md @@ -12,6 +12,16 @@ ng serve Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. +## API Proxy + +Because the solution utilizes Aspire which injects endpoint references for the API as environment variables, this application uses a proxy to access the API. The proxy is configured in the `proxy.config.js` file which is used in the `serve` section of the `angular.json` file. This makes the dev server provide a proxy when serving the application. + +The environment variables for the API endpoint are named `services__Vegasco-Server-Api__https__0` and `services__Vegasco-Server-Api__http__0` for the https and the http endpoints respectively. If the https endpoint is not configured, the http endpoint is used. At least one of them has to be configured. + +To allow the dev proxy to accept otherwise untrusted server certificates, set `NODE_ENV` to `development`. Otherwise the dev proxy rejects untrusted certificates. + +When deploying the application elsewhere, another proxy has to be configured to provide the same functionality to ensure the application works correctly. + ## Code scaffolding Angular CLI includes powerful code scaffolding tools. To generate a new component, run: -- 2.49.1 From 4b1f9e78df68975c301a9809e3b93ce1bfae10d8 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 20 Jun 2025 22:02:25 +0200 Subject: [PATCH 123/150] Fix paths in create migrations script --- Create-Migration.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Create-Migration.ps1 b/Create-Migration.ps1 index 729f335..03f6af2 100644 --- a/Create-Migration.ps1 +++ b/Create-Migration.ps1 @@ -1,2 +1,2 @@ -dotnet ef migrations add $args[0] --project .\src\WebApi\WebApi.csproj --output-dir Persistence/Migrations -dotnet ef migrations script --idempotent --project .\src\WebApi\WebApi.csproj --output migrations/migration.sql +dotnet ef migrations add $args[0] --project .\src\Vegasco.Server.Api\Vegasco.Server.Api.csproj --output-dir Persistence/Migrations +dotnet ef migrations script --idempotent --project .\src\Vegasco.Server.Api\Vegasco.Server.Api.csproj --output ./src/Vegasco.Server.Api/migrations/migration.sql -- 2.49.1 From 69bc76cab455973f6436048676f4cd47f7171183 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 20 Jun 2025 22:02:56 +0200 Subject: [PATCH 124/150] Add idempotent migration script --- .../migrations/migration.sql | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/Vegasco.Server.Api/migrations/migration.sql diff --git a/src/Vegasco.Server.Api/migrations/migration.sql b/src/Vegasco.Server.Api/migrations/migration.sql new file mode 100644 index 0000000..da5b1ad --- /dev/null +++ b/src/Vegasco.Server.Api/migrations/migration.sql @@ -0,0 +1,70 @@ +CREATE TABLE IF NOT EXISTS "__EFMigrationsHistory" ( + "MigrationId" character varying(150) NOT NULL, + "ProductVersion" character varying(32) NOT NULL, + CONSTRAINT "PK___EFMigrationsHistory" PRIMARY KEY ("MigrationId") +); + +START TRANSACTION; + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN + CREATE TABLE "Users" ( + "Id" text NOT NULL, + CONSTRAINT "PK_Users" PRIMARY KEY ("Id") + ); + END IF; +END $EF$; + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN + CREATE TABLE "Cars" ( + "Id" uuid NOT NULL, + "Name" character varying(50) NOT NULL, + "UserId" text NOT NULL, + CONSTRAINT "PK_Cars" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Cars_Users_UserId" FOREIGN KEY ("UserId") REFERENCES "Users" ("Id") ON DELETE CASCADE + ); + END IF; +END $EF$; + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN + CREATE TABLE "Consumptions" ( + "Id" uuid NOT NULL, + "DateTime" timestamp with time zone NOT NULL, + "Distance" double precision NOT NULL, + "Amount" double precision NOT NULL, + "IgnoreInCalculation" boolean NOT NULL, + "CarId" uuid NOT NULL, + CONSTRAINT "PK_Consumptions" PRIMARY KEY ("Id"), + CONSTRAINT "FK_Consumptions_Cars_CarId" FOREIGN KEY ("CarId") REFERENCES "Cars" ("Id") ON DELETE CASCADE + ); + END IF; +END $EF$; + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN + CREATE INDEX "IX_Cars_UserId" ON "Cars" ("UserId"); + END IF; +END $EF$; + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN + CREATE INDEX "IX_Consumptions_CarId" ON "Consumptions" ("CarId"); + END IF; +END $EF$; + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN + INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") + VALUES ('20240818105918_Initial', '9.0.5'); + END IF; +END $EF$; +COMMIT; + -- 2.49.1 From c58f6fe36441d58d27f572799e9788d7fd571b1c Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 22 Jun 2025 11:07:02 +0200 Subject: [PATCH 125/150] Drop IgnoreInCalculation property --- .../Consumptions/Consumption.cs | 5 - .../Consumptions/CreateConsumption.cs | 9 +- .../Consumptions/GetConsumption.cs | 10 +- .../Consumptions/GetConsumptions.cs | 3 +- .../Consumptions/UpdateConsumption.cs | 7 +- ...085121_DropIgnoreInCalculation.Designer.cs | 117 ++++++++++++++++++ .../20250622085121_DropIgnoreInCalculation.cs | 29 +++++ .../ApplicationDbContextModelSnapshot.cs | 6 +- .../migrations/migration.sql | 15 +++ .../ConsumptionFaker.cs | 4 +- .../CreateConsumptionRequestValidatorTests.cs | 1 - .../UpdateConsumptionRequestValidatorTests.cs | 3 +- 12 files changed, 179 insertions(+), 30 deletions(-) create mode 100644 src/Vegasco.Server.Api/Persistence/Migrations/20250622085121_DropIgnoreInCalculation.Designer.cs create mode 100644 src/Vegasco.Server.Api/Persistence/Migrations/20250622085121_DropIgnoreInCalculation.cs diff --git a/src/Vegasco.Server.Api/Consumptions/Consumption.cs b/src/Vegasco.Server.Api/Consumptions/Consumption.cs index 869b6af..ae2024f 100644 --- a/src/Vegasco.Server.Api/Consumptions/Consumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/Consumption.cs @@ -14,8 +14,6 @@ public class Consumption public double Amount { get; set; } - public bool IgnoreInCalculation { get; set; } - public CarId CarId { get; set; } public virtual Car Car { get; set; } = null!; @@ -39,9 +37,6 @@ public class ConsumptionTableConfiguration : IEntityTypeConfiguration x.Amount) .IsRequired(); - builder.Property(x => x.IgnoreInCalculation) - .IsRequired(); - builder.Property(x => x.CarId) .IsRequired() .HasConversion(); diff --git a/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs b/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs index b33a610..4f2fb53 100644 --- a/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs @@ -8,9 +8,9 @@ namespace Vegasco.Server.Api.Consumptions; public static class CreateConsumption { - public record Request(DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId); + public record Request(DateTimeOffset DateTime, double Distance, double Amount, Guid CarId); - public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId); + public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, Guid CarId); public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) { @@ -58,12 +58,11 @@ public static class CreateConsumption return TypedResults.NotFound(); } - var consumption = new Consumption + Consumption consumption = new() { DateTime = request.DateTime.ToUniversalTime(), Distance = request.Distance, Amount = request.Amount, - IgnoreInCalculation = request.IgnoreInCalculation, CarId = new CarId(request.CarId) }; @@ -71,6 +70,6 @@ public static class CreateConsumption await dbContext.SaveChangesAsync(cancellationToken); return TypedResults.Created($"consumptions/{consumption.Id.Value}", - new Response(consumption.Id.Value, consumption.DateTime, consumption.Distance, consumption.Amount, consumption.IgnoreInCalculation, consumption.CarId.Value)); + new Response(consumption.Id.Value, consumption.DateTime, consumption.Distance, consumption.Amount, consumption.CarId.Value)); } } \ No newline at end of file diff --git a/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs b/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs index a8684dd..82b8dc7 100644 --- a/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/GetConsumption.cs @@ -4,7 +4,7 @@ namespace Vegasco.Server.Api.Consumptions; public static class GetConsumption { - public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId); + public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, Guid CarId); public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) { @@ -28,8 +28,12 @@ public static class GetConsumption return TypedResults.NotFound(); } - var response = new Response(consumption.Id.Value, consumption.DateTime, consumption.Distance, - consumption.Amount, consumption.IgnoreInCalculation, consumption.CarId.Value); + Response response = new( + consumption.Id.Value, + consumption.DateTime, + consumption.Distance, + consumption.Amount, + consumption.CarId.Value); return TypedResults.Ok(response); } } \ No newline at end of file diff --git a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs index a472df0..1e07449 100644 --- a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs @@ -17,7 +17,6 @@ public static class GetConsumptions DateTimeOffset DateTime, double Distance, double Amount, - bool IgnoreInCalculation, Guid CarId); public class Request @@ -43,7 +42,7 @@ public static class GetConsumptions List consumptions = await dbContext.Consumptions .OrderByDescending(x => x.DateTime) .Select(x => - new ResponseDto(x.Id.Value, x.DateTime, x.Distance, x.Amount, x.IgnoreInCalculation, x.CarId.Value)) + new ResponseDto(x.Id.Value, x.DateTime, x.Distance, x.Amount, x.CarId.Value)) .ToListAsync(cancellationToken); ApiResponse apiResponse = new() diff --git a/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs b/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs index cb07f24..c598779 100644 --- a/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs @@ -7,9 +7,9 @@ namespace Vegasco.Server.Api.Consumptions; public static class UpdateConsumption { - public record Request(DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation); + public record Request(DateTimeOffset DateTime, double Distance, double Amount); - public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId); + public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, Guid CarId); public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) { @@ -60,10 +60,9 @@ public static class UpdateConsumption consumption.DateTime = request.DateTime.ToUniversalTime(); consumption.Distance = request.Distance; consumption.Amount = request.Amount; - consumption.IgnoreInCalculation = request.IgnoreInCalculation; await dbContext.SaveChangesAsync(cancellationToken); - return TypedResults.Ok(new Response(consumption.Id.Value, consumption.DateTime, consumption.Distance, consumption.Amount, consumption.IgnoreInCalculation, consumption.CarId.Value)); + return TypedResults.Ok(new Response(consumption.Id.Value, consumption.DateTime, consumption.Distance, consumption.Amount, consumption.CarId.Value)); } } \ No newline at end of file diff --git a/src/Vegasco.Server.Api/Persistence/Migrations/20250622085121_DropIgnoreInCalculation.Designer.cs b/src/Vegasco.Server.Api/Persistence/Migrations/20250622085121_DropIgnoreInCalculation.Designer.cs new file mode 100644 index 0000000..0fefbc7 --- /dev/null +++ b/src/Vegasco.Server.Api/Persistence/Migrations/20250622085121_DropIgnoreInCalculation.Designer.cs @@ -0,0 +1,117 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Vegasco.Server.Api.Persistence; + +#nullable disable + +namespace Vegasco.Server.Api.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20250622085121_DropIgnoreInCalculation")] + partial class DropIgnoreInCalculation + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Vegasco.Server.Api.Cars.Car", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Cars"); + }); + + modelBuilder.Entity("Vegasco.Server.Api.Consumptions.Consumption", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("double precision"); + + b.Property("CarId") + .HasColumnType("uuid"); + + b.Property("DateTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Distance") + .HasColumnType("double precision"); + + b.HasKey("Id"); + + b.HasIndex("CarId"); + + b.ToTable("Consumptions"); + }); + + modelBuilder.Entity("Vegasco.Server.Api.Users.User", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Vegasco.Server.Api.Cars.Car", b => + { + b.HasOne("Vegasco.Server.Api.Users.User", "User") + .WithMany("Cars") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Vegasco.Server.Api.Consumptions.Consumption", b => + { + b.HasOne("Vegasco.Server.Api.Cars.Car", "Car") + .WithMany("Consumptions") + .HasForeignKey("CarId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Car"); + }); + + modelBuilder.Entity("Vegasco.Server.Api.Cars.Car", b => + { + b.Navigation("Consumptions"); + }); + + modelBuilder.Entity("Vegasco.Server.Api.Users.User", b => + { + b.Navigation("Cars"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Vegasco.Server.Api/Persistence/Migrations/20250622085121_DropIgnoreInCalculation.cs b/src/Vegasco.Server.Api/Persistence/Migrations/20250622085121_DropIgnoreInCalculation.cs new file mode 100644 index 0000000..0a82444 --- /dev/null +++ b/src/Vegasco.Server.Api/Persistence/Migrations/20250622085121_DropIgnoreInCalculation.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Vegasco.Server.Api.Persistence.Migrations +{ + /// + public partial class DropIgnoreInCalculation : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "IgnoreInCalculation", + table: "Consumptions"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "IgnoreInCalculation", + table: "Consumptions", + type: "boolean", + nullable: false, + defaultValue: false); + } + } +} diff --git a/src/Vegasco.Server.Api/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/Vegasco.Server.Api/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index c29dadd..6c23abf 100644 --- a/src/Vegasco.Server.Api/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/Vegasco.Server.Api/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -6,7 +6,6 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Vegasco.Server.Api.Persistence; - #nullable disable namespace Vegasco.Server.Api.Persistence.Migrations @@ -18,7 +17,7 @@ namespace Vegasco.Server.Api.Persistence.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("ProductVersion", "9.0.5") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -61,9 +60,6 @@ namespace Vegasco.Server.Api.Persistence.Migrations b.Property("Distance") .HasColumnType("double precision"); - b.Property("IgnoreInCalculation") - .HasColumnType("boolean"); - b.HasKey("Id"); b.HasIndex("CarId"); diff --git a/src/Vegasco.Server.Api/migrations/migration.sql b/src/Vegasco.Server.Api/migrations/migration.sql index da5b1ad..545aaff 100644 --- a/src/Vegasco.Server.Api/migrations/migration.sql +++ b/src/Vegasco.Server.Api/migrations/migration.sql @@ -66,5 +66,20 @@ BEGIN VALUES ('20240818105918_Initial', '9.0.5'); END IF; END $EF$; + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20250622085121_DropIgnoreInCalculation') THEN + ALTER TABLE "Consumptions" DROP COLUMN "IgnoreInCalculation"; + END IF; +END $EF$; + +DO $EF$ +BEGIN + IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20250622085121_DropIgnoreInCalculation') THEN + INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") + VALUES ('20250622085121_DropIgnoreInCalculation', '9.0.5'); + END IF; +END $EF$; COMMIT; diff --git a/tests/Vegasco.Server.Api.Tests.Integration/ConsumptionFaker.cs b/tests/Vegasco.Server.Api.Tests.Integration/ConsumptionFaker.cs index 681ea4a..36aece2 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/ConsumptionFaker.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/ConsumptionFaker.cs @@ -13,7 +13,6 @@ internal class ConsumptionFaker _faker.Date.RecentOffset(), _faker.Random.Int(1, 1_000), _faker.Random.Int(20, 70), - _faker.Random.Bool(), carId); } @@ -23,7 +22,6 @@ internal class ConsumptionFaker return new UpdateConsumption.Request( createRequest.DateTime, createRequest.Distance, - createRequest.Amount, - createRequest.IgnoreInCalculation); + createRequest.Amount); } } \ No newline at end of file diff --git a/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs b/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs index 2c7c3ab..0ede696 100644 --- a/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs @@ -22,7 +22,6 @@ public class CreateConsumptionRequestValidatorTests _utcNow.AddDays(-1), 1, 1, - false, Guid.NewGuid()); } diff --git a/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs b/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs index fd34167..ef8d571 100644 --- a/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs @@ -22,8 +22,7 @@ public class UpdateConsumptionRequestValidatorTests _validRequest = new UpdateConsumption.Request( _utcNow.AddDays(-1), 1, - 1, - false); + 1); } [Fact] -- 2.49.1 From a997a3b825711c5cd9e4e6543ba4225119d170ee Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 22 Jun 2025 11:51:02 +0200 Subject: [PATCH 126/150] Remove ignoreInCalculation from Frontend --- src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts | 1 - .../src/app/api/consumptions/create-consumption-entry.ts | 1 - .../src/app/api/consumptions/update-consumption-entry.ts | 1 - .../src/app/modules/entries/edit-entry/edit-entry.component.ts | 1 - 4 files changed, 4 deletions(-) diff --git a/src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts b/src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts index 66cd3fc..89774b5 100644 --- a/src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts +++ b/src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts @@ -3,6 +3,5 @@ interface ConsumptionEntry { dateTime: string; distance: number; amount: number; - ignoreInCalculation: boolean; carId: string; } diff --git a/src/Vegasco-Web/src/app/api/consumptions/create-consumption-entry.ts b/src/Vegasco-Web/src/app/api/consumptions/create-consumption-entry.ts index fadf32f..dea5b78 100644 --- a/src/Vegasco-Web/src/app/api/consumptions/create-consumption-entry.ts +++ b/src/Vegasco-Web/src/app/api/consumptions/create-consumption-entry.ts @@ -2,6 +2,5 @@ interface CreateConsumptionEntry { dateTime: string; distance: number; amount: number; - ignoreInCalculation: boolean; carId: string; } diff --git a/src/Vegasco-Web/src/app/api/consumptions/update-consumption-entry.ts b/src/Vegasco-Web/src/app/api/consumptions/update-consumption-entry.ts index fcdd0f9..41e169c 100644 --- a/src/Vegasco-Web/src/app/api/consumptions/update-consumption-entry.ts +++ b/src/Vegasco-Web/src/app/api/consumptions/update-consumption-entry.ts @@ -2,6 +2,5 @@ interface UpdateConsumptionEntry { dateTime: string; distance: number; amount: number; - ignoreInCalculation: boolean; carId: string; } diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts index 9fbf386..e485b49 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts @@ -183,7 +183,6 @@ export class EditEntryComponent implements OnInit { dateTime: dateTime.toISOString(), distance: this.formGroup.controls[this.formFieldNames.mileage].value!, amount: this.formGroup.controls[this.formFieldNames.amount].value!, - ignoreInCalculation: false, }; } -- 2.49.1 From cb3c8c0d18088cfee579bbe694435f316c78e276 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 22 Jun 2025 11:51:38 +0200 Subject: [PATCH 127/150] Include necessary info directly in get consumption entries response dto --- .../api/consumptions/consumption-client.ts | 12 +++-- .../app/api/consumptions/consumption-entry.ts | 2 +- .../get-consumption-entries-entry.ts | 10 ++++ .../get-consumption-entries-response.ts | 8 +-- .../src/app/api/models/consumption.ts | 9 ---- .../entry-card/entry-card.component.ts | 18 +++---- .../entries/entries/entries.component.ts | 33 ++++++------ .../services/entries-overview.service.ts | 50 ------------------- .../Consumptions/GetConsumptions.cs | 47 ++++++++++++++--- .../Consumptions/GetConsumptionsTests.cs | 32 ++++++++---- 10 files changed, 114 insertions(+), 107 deletions(-) create mode 100644 src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-entry.ts delete mode 100644 src/Vegasco-Web/src/app/api/models/consumption.ts delete mode 100644 src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts diff --git a/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts b/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts index 1646cba..fa533ec 100644 --- a/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts +++ b/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts @@ -1,14 +1,16 @@ -import {inject, Injectable} from '@angular/core'; -import {HttpClient} from '@angular/common/http'; -import {API_BASE_PATH} from '../api-base-path'; -import {map, Observable} from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { inject, Injectable } from '@angular/core'; +import { GetConsumptionEntriesResponse } from '@vegasco-web/api/consumptions/get-consumption-entries-response'; +import { map, Observable } from 'rxjs'; +import { API_BASE_PATH } from '../api-base-path'; +import { ConsumptionEntry } from './consumption-entry'; @Injectable({ providedIn: 'root', }) export class ConsumptionClient { private readonly http = inject(HttpClient); - private readonly apiBasePath = inject(API_BASE_PATH, {optional: true}); + private readonly apiBasePath = inject(API_BASE_PATH, { optional: true }); getAll(): Observable { return this.http.get(`${this.apiBasePath}/v1/consumptions`); diff --git a/src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts b/src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts index 89774b5..cff0df0 100644 --- a/src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts +++ b/src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts @@ -1,4 +1,4 @@ -interface ConsumptionEntry { +export interface ConsumptionEntry { id: string; dateTime: string; distance: number; diff --git a/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-entry.ts b/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-entry.ts new file mode 100644 index 0000000..1fcd163 --- /dev/null +++ b/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-entry.ts @@ -0,0 +1,10 @@ +export interface GetConsumptionEntriesEntry { + id: string; + dateTime: string; + distance: number; + amount: number; + car: { + id: string; + name: string; + }; +} diff --git a/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-response.ts b/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-response.ts index c4099c4..56e2f15 100644 --- a/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-response.ts +++ b/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-response.ts @@ -1,3 +1,5 @@ -interface GetConsumptionEntriesResponse { - consumptions: ConsumptionEntry[]; -} \ No newline at end of file +import { GetConsumptionEntriesEntry } from "./get-consumption-entries-entry"; + +export interface GetConsumptionEntriesResponse { + consumptions: GetConsumptionEntriesEntry[]; +} diff --git a/src/Vegasco-Web/src/app/api/models/consumption.ts b/src/Vegasco-Web/src/app/api/models/consumption.ts deleted file mode 100644 index 0462c83..0000000 --- a/src/Vegasco-Web/src/app/api/models/consumption.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface Consumption { - id: string; - dateTime: string; - distance: number; - amount: number; - ignoreInCalculation: boolean; - carId: string; - car: Car; -} \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts index 6945d4f..59d3ffc 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts @@ -3,17 +3,17 @@ import { HttpErrorResponse } from '@angular/common/http'; import { Component, DestroyRef, inject, input, output } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; -import { - matCalendarMonthSharp, - matDeleteSharp, - matStraightenSharp, - matLocalGasStationSharp, -} from '@ng-icons/material-icons/sharp'; import { matDirectionsCarOutline, } from '@ng-icons/material-icons/outline'; +import { + matCalendarMonthSharp, + matDeleteSharp, + matLocalGasStationSharp, + matStraightenSharp, +} from '@ng-icons/material-icons/sharp'; import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; -import { Consumption } from '@vegasco-web/api/models/consumption'; +import { GetConsumptionEntriesEntry } from '@vegasco-web/api/consumptions/get-consumption-entries-entry'; import { RoutingService } from '@vegasco-web/services/routing.service'; import { ConfirmationService, MessageService } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; @@ -45,9 +45,9 @@ import { catchError, EMPTY, Observable, tap, throwError } from 'rxjs'; styleUrl: './entry-card.component.scss' }) export class EntryCardComponent { - readonly entry = input.required(); + readonly entry = input.required(); - readonly entryDeleted = output(); + readonly entryDeleted = output(); private readonly routingService = inject(RoutingService); private readonly consumptionClient = inject(ConsumptionClient); diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index 08e33e9..ffc0f62 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -1,8 +1,16 @@ import { AsyncPipe, CommonModule } from '@angular/common'; +import { HttpErrorResponse } from '@angular/common/http'; import { Component, DestroyRef, inject, OnInit } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { RouterLink } from '@angular/router'; +import { NgIconComponent, provideIcons } from '@ng-icons/core'; +import { + matAddSharp, +} from '@ng-icons/material-icons/sharp'; +import { CarClient } from '@vegasco-web/api/cars/car-client'; +import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; +import { GetConsumptionEntriesEntry } from '@vegasco-web/api/consumptions/get-consumption-entries-entry'; import { MessageService } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; import { DataViewModule } from 'primeng/dataview'; @@ -20,14 +28,9 @@ import { tap, throwError } from 'rxjs'; -import { EntriesOverviewService } from './services/entries-overview.service'; -import { EntryCardComponent } from './components/entry-card/entry-card.component'; -import { - matAddSharp, -} from '@ng-icons/material-icons/sharp'; -import { NgIconComponent, provideIcons } from '@ng-icons/core'; -import { HttpErrorResponse } from '@angular/common/http'; import { SelectedCarService } from '../services/selected-car.service'; +import { EntryCardComponent } from './components/entry-card/entry-card.component'; +import { ConsumptionEntry } from '@vegasco-web/api/consumptions/consumption-entry'; @Component({ selector: 'app-entries', @@ -48,18 +51,18 @@ import { SelectedCarService } from '../services/selected-car.service'; provideIcons({ matAddSharp, }), - EntriesOverviewService, ], templateUrl: './entries.component.html', styleUrl: './entries.component.scss' }) export class EntriesComponent implements OnInit { - private readonly entriesOverviewService = inject(EntriesOverviewService); + private readonly carClient = inject(CarClient); + private readonly consumptionClient = inject(ConsumptionClient); private readonly messageService = inject(MessageService); private readonly selectedCarService = inject(SelectedCarService); private readonly destroyRef = inject(DestroyRef); - protected readonly consumptionEntries$: Observable; + protected readonly consumptionEntries$: Observable; protected readonly cars$: Observable; protected readonly skeletonsIterationSource = Array(10).fill(0); @@ -69,9 +72,10 @@ export class EntriesComponent implements OnInit { private readonly deletedEntries$ = new BehaviorSubject([]); constructor() { - const entries = this.entriesOverviewService.getEntries() + const entries = this.consumptionClient.getAll() .pipe( takeUntilDestroyed(), + map(response => response.consumptions), catchError((error) => this.handleGetEntriesError(error)) ); @@ -92,13 +96,14 @@ export class EntriesComponent implements OnInit { return nonDeletedEntries; } - return nonDeletedEntries.filter(entry => entry.carId === selectedCar.id); + return nonDeletedEntries.filter(entry => entry.car.id === selectedCar.id); }) ); - this.cars$ = this.entriesOverviewService.getCars() + this.cars$ = this.carClient.getAll() .pipe( takeUntilDestroyed(), + map(response => response.cars), map((cars) => cars .sort((a, b) => a.name.localeCompare(b.name))), tap((cars) => { @@ -129,7 +134,7 @@ export class EntriesComponent implements OnInit { .subscribe(); } - onEntryDeleted(entry: ConsumptionEntry): void { + onEntryDeleted(entry: GetConsumptionEntriesEntry): void { this.deletedEntries$.next([...this.deletedEntries$.value, entry.id]); this.messageService.add({ severity: 'success', diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts b/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts deleted file mode 100644 index c026496..0000000 --- a/src/Vegasco-Web/src/app/modules/entries/entries/services/entries-overview.service.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { inject, Injectable } from "@angular/core"; -import { CarClient } from "@vegasco-web/api/cars/car-client"; -import { ConsumptionClient } from "@vegasco-web/api/consumptions/consumption-client"; -import { Consumption } from "@vegasco-web/api/models/consumption"; -import { RoutingService } from "@vegasco-web/services/routing.service"; -import { combineLatest, map, Observable, shareReplay } from "rxjs"; - -@Injectable() -export class EntriesOverviewService { - private readonly carClient = inject(CarClient); - private readonly consumptionClient = inject(ConsumptionClient); - - private cachedCars$: Observable | null = null; - - private ensureCarsAreCached(): void { - if (this.cachedCars$ !== null) { - return; - } - - this.cachedCars$ = this.carClient.getAll() - .pipe( - map(response => response.cars), - shareReplay(1) - ); - } - - getEntries(): Observable { - this.ensureCarsAreCached(); - - const entries$ = this.consumptionClient.getAll() - .pipe(map(response => response.consumptions)); - - return combineLatest([this.cachedCars$!, entries$]) - .pipe( - map(([cars, entries]) => { - return entries - .sort((a, b) => b.dateTime.localeCompare(a.dateTime)) - .map((entry): Consumption => ({ - ...entry, - car: cars.find(car => car.id === entry.carId)! - })); - }) - ) - } - - getCars(): Observable { - this.ensureCarsAreCached(); - return this.cachedCars$!; - } -} \ No newline at end of file diff --git a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs index 1e07449..6f59411 100644 --- a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Vegasco.Server.Api.Cars; using Vegasco.Server.Api.Persistence; namespace Vegasco.Server.Api.Consumptions; @@ -17,7 +18,18 @@ public static class GetConsumptions DateTimeOffset DateTime, double Distance, double Amount, - Guid CarId); + CarDto Car, + double? LiterPer100Km); + + public record CarDto( + Guid Id, + string Name) + { + public static CarDto FromCar(Car car) + { + return new CarDto(car.Id.Value, car.Name); + } + } public class Request { @@ -39,16 +51,37 @@ public static class GetConsumptions ApplicationDbContext dbContext, CancellationToken cancellationToken) { - List consumptions = await dbContext.Consumptions + List consumptions = await dbContext.Consumptions .OrderByDescending(x => x.DateTime) - .Select(x => - new ResponseDto(x.Id.Value, x.DateTime, x.Distance, x.Amount, x.CarId.Value)) + .Include(x => x.Car) .ToListAsync(cancellationToken); - ApiResponse apiResponse = new() + List responses = []; + + for (int i = 0; i < consumptions.Count; i++) { - Consumptions = consumptions - }; + Consumption consumption = consumptions[i]; + + double? literPer100Km = null; + + bool isLast = i == consumptions.Count - 1; + if (!isLast) + { + Consumption previousConsumption = consumptions[i + 1]; + double distanceDiff = consumption.Distance - previousConsumption.Distance; + literPer100Km = consumption.Amount / (distanceDiff / 100); + } + + responses.Add(new ResponseDto( + consumption.Id.Value, + consumption.DateTime, + consumption.Distance, + consumption.Amount, + CarDto.FromCar(consumption.Car), + literPer100Km)); + } + + ApiResponse apiResponse = new() { Consumptions = responses }; return TypedResults.Ok(apiResponse); } } \ No newline at end of file diff --git a/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionsTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionsTests.cs index f93ee0a..66ac6bb 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionsTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionsTests.cs @@ -31,7 +31,7 @@ public class GetConsumptionsTests : IAsyncLifetime // Arrange List createdConsumptions = []; const int numberOfConsumptions = 3; - for (var i = 0; i < numberOfConsumptions; i++) + for (int i = 0; i < numberOfConsumptions; i++) { CreateConsumption.Response createdConsumption = await CreateConsumptionAsync(); createdConsumptions.Add(createdConsumption); @@ -42,8 +42,16 @@ public class GetConsumptionsTests : IAsyncLifetime // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); - var apiResponse = await response.Content.ReadFromJsonAsync(); - apiResponse!.Consumptions.Should().BeEquivalentTo(createdConsumptions); + GetConsumptions.ApiResponse? apiResponse = + await response.Content.ReadFromJsonAsync(); + + apiResponse.Should().NotBeNull(); + apiResponse.Consumptions.Should().HaveCount(createdConsumptions.Count); + apiResponse.Consumptions.Should().BeEquivalentTo(createdConsumptions, o => o.ExcludingMissingMembers()); + apiResponse.Consumptions + .Select(x => x.Car.Id) + .Should() + .BeEquivalentTo(createdConsumptions, o => o.ExcludingMissingMembers()); } [Fact] @@ -56,26 +64,32 @@ public class GetConsumptionsTests : IAsyncLifetime // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); - var apiResponse = await response.Content.ReadFromJsonAsync(); + GetConsumptions.ApiResponse? apiResponse = + await response.Content.ReadFromJsonAsync(); apiResponse!.Consumptions.Should().BeEmpty(); } private async Task CreateConsumptionAsync() { CreateCar.Response createdCarResponse = await CreateCarAsync(); - CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id); - using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest); + CreateConsumption.Request createConsumptionRequest = + _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id); + using HttpResponseMessage response = + await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest); response.EnsureSuccessStatusCode(); - var createdConsumption = await response.Content.ReadFromJsonAsync(); + CreateConsumption.Response? createdConsumption = + await response.Content.ReadFromJsonAsync(); return createdConsumption!; } private async Task CreateCarAsync() { CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest(); - using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); + using HttpResponseMessage createCarResponse = + await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); - var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); + CreateCar.Response? createdCarResponse = + await createCarResponse.Content.ReadFromJsonAsync(); return createdCarResponse!; } -- 2.49.1 From f7af144275dc72be73d7662f2282a9eba4738109 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 22 Jun 2025 12:36:19 +0200 Subject: [PATCH 128/150] Display consumption in liter per 100 km --- .../api/consumptions/consumption-client.ts | 2 -- .../app/api/consumptions/consumption-entry.ts | 2 +- .../get-consumption-entries-entry.ts | 3 ++- .../get-consumption-entries-response.ts | 4 +--- .../entry-card/entry-card.component.html | 19 ++++++++++------ .../entry-card/entry-card.component.scss | 2 +- .../entry-card/entry-card.component.ts | 22 +++++++++++++------ .../fraction/fraction.component.html | 9 ++++++++ .../fraction/fraction.component.scss | 11 ++++++++++ .../components/fraction/fraction.component.ts | 12 ++++++++++ .../entries/entries/entries.component.ts | 4 +--- 11 files changed, 65 insertions(+), 25 deletions(-) create mode 100644 src/Vegasco-Web/src/app/modules/entries/entries/components/fraction/fraction.component.html create mode 100644 src/Vegasco-Web/src/app/modules/entries/entries/components/fraction/fraction.component.scss create mode 100644 src/Vegasco-Web/src/app/modules/entries/entries/components/fraction/fraction.component.ts diff --git a/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts b/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts index fa533ec..c360d2c 100644 --- a/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts +++ b/src/Vegasco-Web/src/app/api/consumptions/consumption-client.ts @@ -1,9 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; -import { GetConsumptionEntriesResponse } from '@vegasco-web/api/consumptions/get-consumption-entries-response'; import { map, Observable } from 'rxjs'; import { API_BASE_PATH } from '../api-base-path'; -import { ConsumptionEntry } from './consumption-entry'; @Injectable({ providedIn: 'root', diff --git a/src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts b/src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts index cff0df0..89774b5 100644 --- a/src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts +++ b/src/Vegasco-Web/src/app/api/consumptions/consumption-entry.ts @@ -1,4 +1,4 @@ -export interface ConsumptionEntry { +interface ConsumptionEntry { id: string; dateTime: string; distance: number; diff --git a/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-entry.ts b/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-entry.ts index 1fcd163..2585c9b 100644 --- a/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-entry.ts +++ b/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-entry.ts @@ -1,4 +1,4 @@ -export interface GetConsumptionEntriesEntry { +interface GetConsumptionEntriesEntry { id: string; dateTime: string; distance: number; @@ -7,4 +7,5 @@ export interface GetConsumptionEntriesEntry { id: string; name: string; }; + literPer100Km: number | null; } diff --git a/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-response.ts b/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-response.ts index 56e2f15..a18ec60 100644 --- a/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-response.ts +++ b/src/Vegasco-Web/src/app/api/consumptions/get-consumption-entries-response.ts @@ -1,5 +1,3 @@ -import { GetConsumptionEntriesEntry } from "./get-consumption-entries-entry"; - -export interface GetConsumptionEntriesResponse { +interface GetConsumptionEntriesResponse { consumptions: GetConsumptionEntriesEntry[]; } diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.html index 0140a69..ae2476a 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.html @@ -12,13 +12,6 @@
-
-
- -
{{ entry().car.name }}
-
-
-
@@ -32,6 +25,18 @@
{{entry().amount }} ℓ
+ +
+ @if (formattedLiterPer100Km(); as formattedLiterPer100Km) { +
+ +
+ {{ formattedLiterPer100Km }} + +
+
+ } +
diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.scss b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.scss index 2006a40..42ece6f 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.scss +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.scss @@ -1,3 +1,3 @@ .edit-button { cursor: pointer; -} \ No newline at end of file +} diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts index 59d3ffc..39f8cea 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts @@ -1,26 +1,23 @@ import { DatePipe } from '@angular/common'; import { HttpErrorResponse } from '@angular/common/http'; -import { Component, DestroyRef, inject, input, output } from '@angular/core'; +import { Component, computed, DestroyRef, inject, input, output } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { NgIconComponent, provideIcons } from '@ng-icons/core'; -import { - matDirectionsCarOutline, -} from '@ng-icons/material-icons/outline'; import { matCalendarMonthSharp, matDeleteSharp, matLocalGasStationSharp, + matSpeedSharp, matStraightenSharp, } from '@ng-icons/material-icons/sharp'; import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; -import { GetConsumptionEntriesEntry } from '@vegasco-web/api/consumptions/get-consumption-entries-entry'; import { RoutingService } from '@vegasco-web/services/routing.service'; import { ConfirmationService, MessageService } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; import { CardModule } from 'primeng/card'; import { ConfirmDialogModule } from 'primeng/confirmdialog'; import { catchError, EMPTY, Observable, tap, throwError } from 'rxjs'; - +import { FractionComponent } from "../fraction/fraction.component"; @Component({ selector: 'app-entry-card', @@ -30,12 +27,13 @@ import { catchError, EMPTY, Observable, tap, throwError } from 'rxjs'; ConfirmDialogModule, DatePipe, NgIconComponent, + FractionComponent ], providers: [ provideIcons({ matDeleteSharp, matCalendarMonthSharp, - matDirectionsCarOutline, + matSpeedSharp, matStraightenSharp, matLocalGasStationSharp, }), @@ -47,6 +45,16 @@ import { catchError, EMPTY, Observable, tap, throwError } from 'rxjs'; export class EntryCardComponent { readonly entry = input.required(); + protected readonly formattedLiterPer100Km = computed(() => { + const entry = this.entry(); + + const formatted = entry.literPer100Km + ?.toFixed(2) + .replace('.', ','); + console.log('Formatted liter per 100km:', formatted); + return formatted; + }) + readonly entryDeleted = output(); private readonly routingService = inject(RoutingService); diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/fraction/fraction.component.html b/src/Vegasco-Web/src/app/modules/entries/entries/components/fraction/fraction.component.html new file mode 100644 index 0000000..896a396 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/fraction/fraction.component.html @@ -0,0 +1,9 @@ +
+ + {{ numerator() }} + + + + {{ denominator() }} + +
diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/fraction/fraction.component.scss b/src/Vegasco-Web/src/app/modules/entries/entries/components/fraction/fraction.component.scss new file mode 100644 index 0000000..a8afa0b --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/fraction/fraction.component.scss @@ -0,0 +1,11 @@ +.separator { + border-bottom-width: 1px; + width: 100% +} + +.text-half-size { + // Specifically use em here to allow the parent to control the font size + // The font size here should be smaller because it is a fraction and would + // otherwise look too large + font-size: 0.75em; +} diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/fraction/fraction.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/components/fraction/fraction.component.ts new file mode 100644 index 0000000..73fc12f --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/fraction/fraction.component.ts @@ -0,0 +1,12 @@ +import { Component, input } from '@angular/core'; + +@Component({ + selector: 'app-fraction', + imports: [], + templateUrl: './fraction.component.html', + styleUrl: './fraction.component.scss' +}) +export class FractionComponent { + readonly numerator = input.required(); + readonly denominator = input.required(); +} diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index ffc0f62..23d19ed 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -10,7 +10,6 @@ import { } from '@ng-icons/material-icons/sharp'; import { CarClient } from '@vegasco-web/api/cars/car-client'; import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; -import { GetConsumptionEntriesEntry } from '@vegasco-web/api/consumptions/get-consumption-entries-entry'; import { MessageService } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; import { DataViewModule } from 'primeng/dataview'; @@ -30,7 +29,6 @@ import { } from 'rxjs'; import { SelectedCarService } from '../services/selected-car.service'; import { EntryCardComponent } from './components/entry-card/entry-card.component'; -import { ConsumptionEntry } from '@vegasco-web/api/consumptions/consumption-entry'; @Component({ selector: 'app-entries', @@ -97,7 +95,7 @@ export class EntriesComponent implements OnInit { } return nonDeletedEntries.filter(entry => entry.car.id === selectedCar.id); - }) + }), ); this.cars$ = this.carClient.getAll() -- 2.49.1 From 4a8e3d02e06528e78d7753557cf714ada2af3428 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sun, 22 Jun 2025 13:18:22 +0200 Subject: [PATCH 129/150] Fix date times --- src/Vegasco-Web/package.json | 1 + src/Vegasco-Web/pnpm-lock.yaml | 8 ++++++++ .../edit-entry/edit-entry.component.html | 2 ++ .../entries/edit-entry/edit-entry.component.ts | 18 ++++++++++++++++-- .../entry-card/entry-card.component.ts | 1 - .../Consumptions/CreateConsumption.cs | 10 ++++++++-- .../Consumptions/UpdateConsumption.cs | 7 ++++++- .../CreateConsumptionRequestValidatorTests.cs | 6 +++--- .../UpdateConsumptionRequestValidatorTests.cs | 6 +++--- 9 files changed, 47 insertions(+), 12 deletions(-) diff --git a/src/Vegasco-Web/package.json b/src/Vegasco-Web/package.json index 14d4723..5d7764e 100644 --- a/src/Vegasco-Web/package.json +++ b/src/Vegasco-Web/package.json @@ -23,6 +23,7 @@ "@ng-icons/material-icons": "^31.4.0", "@primeng/themes": "^19.1.3", "@tailwindcss/postcss": "^4.1.10", + "dayjs": "^1.11.13", "keycloak-angular": "^19.0.2", "postcss": "^8.5.6", "primeng": "^19.1.3", diff --git a/src/Vegasco-Web/pnpm-lock.yaml b/src/Vegasco-Web/pnpm-lock.yaml index 4d19323..9f99747 100644 --- a/src/Vegasco-Web/pnpm-lock.yaml +++ b/src/Vegasco-Web/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: '@tailwindcss/postcss': specifier: ^4.1.10 version: 4.1.10 + dayjs: + specifier: ^1.11.13 + version: 1.11.13 keycloak-angular: specifier: ^19.0.2 version: 19.0.2(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/router@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(@angular/platform-browser@19.2.14(@angular/animations@19.2.14(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(@angular/common@19.2.14(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@19.2.14(rxjs@7.8.2)(zone.js@0.15.1)))(rxjs@7.8.2))(keycloak-js@26.2.0) @@ -2229,6 +2232,9 @@ packages: resolution: {integrity: sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==} engines: {node: '>=4.0'} + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -6763,6 +6769,8 @@ snapshots: date-format@4.0.14: {} + dayjs@1.11.13: {} + debug@2.6.9: dependencies: ms: 2.0.0 diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html index 55de4e4..24bee44 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.html @@ -39,6 +39,8 @@ [firstDayOfWeek]="1" placeholder="Datum auswählen" [showIcon]="true" + [maxDate]="today" + [defaultDate]="today" [inputId]="formFieldNames.date" [formControlName]="formFieldNames.date" styleClass="w-full" diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts index e485b49..3cde557 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts @@ -1,7 +1,8 @@ +import dayjs from 'dayjs'; import { HttpErrorResponse } from '@angular/common/http'; import { Component, computed, DestroyRef, inject, input, OnInit, signal, Signal } from '@angular/core'; import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; -import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { FormControl, FormGroup, ReactiveFormsModule, ValidationErrors, Validators } from '@angular/forms'; import { CarClient } from '@vegasco-web/api/cars/car-client'; import { ConsumptionClient } from '@vegasco-web/api/consumptions/consumption-client'; import { RoutingService } from '@vegasco-web/services/routing.service'; @@ -51,6 +52,8 @@ export class EditEntryComponent implements OnInit { protected readonly id = input(undefined); + protected readonly today = new Date(); + protected readonly formFieldNames = { car: 'car', date: 'date', @@ -60,7 +63,7 @@ export class EditEntryComponent implements OnInit { protected readonly formGroup = new FormGroup({ [this.formFieldNames.car]: new FormControl({ value: null, disabled: true }, [Validators.required]), - [this.formFieldNames.date]: new FormControl({ value: new Date(), disabled: true }, [Validators.required]), + [this.formFieldNames.date]: new FormControl({ value: new Date(), disabled: true }, [Validators.required, this.dateTimeGreaterThanOrEqualToTodayValidator]), [this.formFieldNames.mileage]: new FormControl({ value: null, disabled: true }, [Validators.required, Validators.min(1)]), [this.formFieldNames.amount]: new FormControl({ value: null, disabled: true }, [Validators.required, Validators.min(1)]), }); @@ -271,4 +274,15 @@ export class EditEntryComponent implements OnInit { return EMPTY; } + + private dateTimeGreaterThanOrEqualToTodayValidator(control: FormControl): ValidationErrors | null { + const tomorrowStartOfDay = dayjs().add(1, 'day').startOf('day'); + const controlDate = dayjs(control.value); + + if (controlDate.isBefore(tomorrowStartOfDay)) { + return null; + } + + return { dateTimeGreaterThanOrEqualToToday: true }; + } } diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts index 39f8cea..9c8558f 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/components/entry-card/entry-card.component.ts @@ -51,7 +51,6 @@ export class EntryCardComponent { const formatted = entry.literPer100Km ?.toFixed(2) .replace('.', ','); - console.log('Formatted liter per 100km:', formatted); return formatted; }) diff --git a/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs b/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs index 4f2fb53..9b236d3 100644 --- a/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/CreateConsumption.cs @@ -25,8 +25,13 @@ public static class CreateConsumption { public Validator(TimeProvider timeProvider) { + DateTime todayEndOfDay = timeProvider.GetUtcNow() + .Date + .AddDays(1) + .AddTicks(-1); + RuleFor(x => x.DateTime.ToUniversalTime()) - .LessThanOrEqualTo(timeProvider.GetUtcNow()) + .LessThanOrEqualTo(todayEndOfDay) .WithName(nameof(Request.DateTime)); RuleFor(x => x.Distance) @@ -70,6 +75,7 @@ public static class CreateConsumption await dbContext.SaveChangesAsync(cancellationToken); return TypedResults.Created($"consumptions/{consumption.Id.Value}", - new Response(consumption.Id.Value, consumption.DateTime, consumption.Distance, consumption.Amount, consumption.CarId.Value)); + new Response(consumption.Id.Value, consumption.DateTime, consumption.Distance, consumption.Amount, + consumption.CarId.Value)); } } \ No newline at end of file diff --git a/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs b/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs index c598779..80553c2 100644 --- a/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs +++ b/src/Vegasco.Server.Api/Consumptions/UpdateConsumption.cs @@ -26,8 +26,13 @@ public static class UpdateConsumption { public Validator(TimeProvider timeProvider) { + DateTime todayEndOfDay = timeProvider.GetUtcNow() + .Date + .AddDays(1) + .AddTicks(-1); + RuleFor(x => x.DateTime.ToUniversalTime()) - .LessThanOrEqualTo(timeProvider.GetUtcNow()) + .LessThanOrEqualTo(todayEndOfDay) .WithName(nameof(Request.DateTime)); RuleFor(x => x.Distance) diff --git a/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs b/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs index 0ede696..e60dcca 100644 --- a/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/CreateConsumptionRequestValidatorTests.cs @@ -19,7 +19,7 @@ public class CreateConsumptionRequestValidatorTests _sut = new CreateConsumption.Validator(_timeProvider); _validRequest = new CreateConsumption.Request( - _utcNow.AddDays(-1), + _utcNow.Date.AddDays(1).AddTicks(-1), 1, 1, Guid.NewGuid()); @@ -38,10 +38,10 @@ public class CreateConsumptionRequestValidatorTests } [Fact] - public async Task ValidateAsync_ShouldBeInvalid_WhenDateTimeIsGreaterThanUtcNow() + public async Task ValidateAsync_ShouldBeInvalid_WhenDateTimeIsGreaterThanUtcToday() { // Arrange - CreateConsumption.Request request = _validRequest with { DateTime = _utcNow.AddDays(1) }; + CreateConsumption.Request request = _validRequest with { DateTime = _utcNow.Date.AddDays(1) }; // Act ValidationResult? result = await _sut.ValidateAsync(request); diff --git a/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs b/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs index ef8d571..982458d 100644 --- a/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Unit/Consumptions/UpdateConsumptionRequestValidatorTests.cs @@ -20,7 +20,7 @@ public class UpdateConsumptionRequestValidatorTests _sut = new UpdateConsumption.Validator(_timeProvider); _validRequest = new UpdateConsumption.Request( - _utcNow.AddDays(-1), + _utcNow.Date.AddDays(1).AddTicks(-1), 1, 1); } @@ -38,10 +38,10 @@ public class UpdateConsumptionRequestValidatorTests } [Fact] - public async Task ValidateAsync_ShouldBeInvalid_WhenDateTimeIsGreaterThanUtcNow() + public async Task ValidateAsync_ShouldBeInvalid_WhenDateTimeIsGreaterThanUtcToday() { // Arrange - UpdateConsumption.Request request = _validRequest with { DateTime = _utcNow.AddDays(1) }; + UpdateConsumption.Request request = _validRequest with { DateTime = _utcNow.Date.AddDays(1) }; // Act ValidationResult? result = await _sut.ValidateAsync(request); -- 2.49.1 From fd7a8024a9020026ccc1e927d256f9c4e0cc020a Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 23 Jun 2025 15:20:52 +0200 Subject: [PATCH 130/150] Install dependencies before launching app --- src/Vegasco-Web/package.json | 1 + src/Vegasco.Server.AppHost/Program.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Vegasco-Web/package.json b/src/Vegasco-Web/package.json index 5d7764e..9a18012 100644 --- a/src/Vegasco-Web/package.json +++ b/src/Vegasco-Web/package.json @@ -3,6 +3,7 @@ "version": "0.0.0", "scripts": { "ng": "ng", + "start:withInstall": "pnpm install && pnpm run start", "start": "run-script-os", "start:win32": "ng serve --port %PORT% --configuration development", "start:default": "ng serve --port $PORT --configuration development", diff --git a/src/Vegasco.Server.AppHost/Program.cs b/src/Vegasco.Server.AppHost/Program.cs index 6c3c0c5..7d36ee5 100644 --- a/src/Vegasco.Server.AppHost/Program.cs +++ b/src/Vegasco.Server.AppHost/Program.cs @@ -13,7 +13,7 @@ IResourceBuilder api = builder .WaitFor(postgres); builder - .AddNpmApp("Vegasco-Web", "../Vegasco-Web") + .AddNpmApp("Vegasco-Web", "../Vegasco-Web", scriptName: "start:withInstall") .WithReference(api) .WaitFor(api) .WithHttpEndpoint(port: 44200, env: "PORT", isProxied: false) -- 2.49.1 From 9c372b31a687d4e9912590e90c40a469518e24fb Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 23 Jun 2025 16:20:44 +0200 Subject: [PATCH 131/150] Add managing cars --- src/Vegasco-Web/src/app/app.html | 12 +- src/Vegasco-Web/src/app/app.routes.ts | 4 + src/Vegasco-Web/src/app/app.ts | 4 +- .../src/app/modules/cars/cars.routes.ts | 17 ++ .../app/modules/cars/cars/cars.component.html | 38 +++ .../app/modules/cars/cars/cars.component.scss | 3 + .../app/modules/cars/cars/cars.component.ts | 117 ++++++++++ .../car-card/car-card.component.html | 22 ++ .../car-card/car-card.component.scss | 3 + .../components/car-card/car-card.component.ts | 116 ++++++++++ .../components/required-marker.component.html | 1 + .../components/required-marker.component.scss | 3 + .../components/required-marker.component.ts | 11 + .../cars/edit-car/edit-car.component.html | 31 +++ .../cars/edit-car/edit-car.component.scss | 0 .../cars/edit-car/edit-car.component.ts | 217 ++++++++++++++++++ .../edit-entry/edit-entry.component.ts | 5 + .../src/app/services/routing.service.ts | 12 + 18 files changed, 612 insertions(+), 4 deletions(-) create mode 100644 src/Vegasco-Web/src/app/modules/cars/cars.routes.ts create mode 100644 src/Vegasco-Web/src/app/modules/cars/cars/cars.component.html create mode 100644 src/Vegasco-Web/src/app/modules/cars/cars/cars.component.scss create mode 100644 src/Vegasco-Web/src/app/modules/cars/cars/cars.component.ts create mode 100644 src/Vegasco-Web/src/app/modules/cars/cars/components/car-card/car-card.component.html create mode 100644 src/Vegasco-Web/src/app/modules/cars/cars/components/car-card/car-card.component.scss create mode 100644 src/Vegasco-Web/src/app/modules/cars/cars/components/car-card/car-card.component.ts create mode 100644 src/Vegasco-Web/src/app/modules/cars/edit-car/components/required-marker.component.html create mode 100644 src/Vegasco-Web/src/app/modules/cars/edit-car/components/required-marker.component.scss create mode 100644 src/Vegasco-Web/src/app/modules/cars/edit-car/components/required-marker.component.ts create mode 100644 src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.html create mode 100644 src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.scss create mode 100644 src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.ts diff --git a/src/Vegasco-Web/src/app/app.html b/src/Vegasco-Web/src/app/app.html index 0954df0..3628171 100644 --- a/src/Vegasco-Web/src/app/app.html +++ b/src/Vegasco-Web/src/app/app.html @@ -1,13 +1,21 @@
-
-
+ \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/app.routes.ts b/src/Vegasco-Web/src/app/app.routes.ts index d6a5093..b30947e 100644 --- a/src/Vegasco-Web/src/app/app.routes.ts +++ b/src/Vegasco-Web/src/app/app.routes.ts @@ -9,5 +9,9 @@ export const routes: Routes = [ { path: 'entries', loadChildren: () => import('./modules/entries/entries.routes').then(m => m.routes) + }, + { + path: 'cars', + loadChildren: () => import('./modules/cars/cars.routes').then(m => m.routes) } ]; diff --git a/src/Vegasco-Web/src/app/app.ts b/src/Vegasco-Web/src/app/app.ts index 611b7ec..799a02f 100644 --- a/src/Vegasco-Web/src/app/app.ts +++ b/src/Vegasco-Web/src/app/app.ts @@ -1,12 +1,12 @@ import { Component } from '@angular/core'; -import { RouterOutlet } from '@angular/router'; +import { RouterLink, RouterOutlet } from '@angular/router'; import { MessageService } from 'primeng/api'; import { ToastModule } from 'primeng/toast'; @Component({ selector: 'app-root', - imports: [RouterOutlet, ToastModule], + imports: [RouterLink, RouterOutlet, ToastModule], providers: [MessageService], templateUrl: './app.html', styleUrl: './app.scss' diff --git a/src/Vegasco-Web/src/app/modules/cars/cars.routes.ts b/src/Vegasco-Web/src/app/modules/cars/cars.routes.ts new file mode 100644 index 0000000..889800c --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/cars/cars.routes.ts @@ -0,0 +1,17 @@ +import { Routes } from "@angular/router"; +import { CarsComponent } from "./cars/cars.component"; + +export const routes: Routes = [ + { + path: '', + component: CarsComponent + }, + { + path: 'create', + loadComponent: () => import('./edit-car/edit-car.component').then(m => m.EditCarComponent) + }, + { + path: 'edit/:id', + loadComponent: () => import('./edit-car/edit-car.component').then(m => m.EditCarComponent) + } +]; \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/cars/cars/cars.component.html b/src/Vegasco-Web/src/app/modules/cars/cars/cars.component.html new file mode 100644 index 0000000..766bdf8 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/cars/cars/cars.component.html @@ -0,0 +1,38 @@ +
+ +
+
+ + + +
+
+
+ @if (nonDeletedCars$ | async; as cars) { + + +
+ @for (car of cars; track car.id) { + + } +
+
+
+ } @else { +
+ @for (_ of skeletonsIterationSource; track $index) { + + } +
+ } +
+
diff --git a/src/Vegasco-Web/src/app/modules/cars/cars/cars.component.scss b/src/Vegasco-Web/src/app/modules/cars/cars/cars.component.scss new file mode 100644 index 0000000..e71c33b --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/cars/cars/cars.component.scss @@ -0,0 +1,3 @@ +th, td { + padding: 0.5rem; +} \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/cars/cars/cars.component.ts b/src/Vegasco-Web/src/app/modules/cars/cars/cars.component.ts new file mode 100644 index 0000000..c6d1da2 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/cars/cars/cars.component.ts @@ -0,0 +1,117 @@ +import { AsyncPipe, CommonModule } from '@angular/common'; +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, DestroyRef, inject } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { RouterLink } from '@angular/router'; +import { NgIconComponent, provideIcons } from '@ng-icons/core'; +import { + matAddSharp, +} from '@ng-icons/material-icons/sharp'; +import { CarClient } from '@vegasco-web/api/cars/car-client'; +import { MessageService } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; +import { DataViewModule } from 'primeng/dataview'; +import { ScrollTopModule } from 'primeng/scrolltop'; +import { SelectModule } from 'primeng/select'; +import { SkeletonModule } from 'primeng/skeleton'; +import { + BehaviorSubject, + catchError, + combineLatest, + EMPTY, + map, + Observable, + throwError +} from 'rxjs'; +import { CarCardComponent } from './components/car-card/car-card.component'; + +@Component({ + selector: 'app-entries', + imports: [ + AsyncPipe, + ButtonModule, + CommonModule, + DataViewModule, + CarCardComponent, + NgIconComponent, + ReactiveFormsModule, + RouterLink, + ScrollTopModule, + SelectModule, + SkeletonModule, + ], + providers: [ + provideIcons({ + matAddSharp, + }), + ], + templateUrl: './cars.component.html', + styleUrl: './cars.component.scss' +}) +export class CarsComponent { + private readonly carClient = inject(CarClient); + private readonly messageService = inject(MessageService); + + protected readonly nonDeletedCars$: Observable; + + protected readonly skeletonsIterationSource = Array(10).fill(0); + + private readonly deletedCars$ = new BehaviorSubject([]); + + constructor() { + const cars$ = this.carClient.getAll() + .pipe( + map(response => response.cars), + map((cars) => cars + .sort((a, b) => a.name.localeCompare(b.name))), + ); + + this.nonDeletedCars$ = combineLatest([ + cars$, + this.deletedCars$ + ]) + .pipe( + takeUntilDestroyed(), + map(([cars, deletedCars]) => cars.filter(car => !deletedCars.includes(car.id))), + catchError((error) => this.handleGetCarsError(error)), + ); + } + + onCarDeleted(entry: Car): void { + this.deletedCars$.next([...this.deletedCars$.value, entry.id]); + this.messageService.add({ + severity: 'success', + summary: 'Auto gelöscht', + detail: 'Das Auto wurde erfolgreich gelöscht.', + }); + } + + private handleGetCarsError(error: unknown): Observable { + if (!(error instanceof HttpErrorResponse)) { + return throwError(() => new Error('An unexpected error occurred')); + } + + switch (true) { + case error.status >= 500 && error.status <= 599: + this.messageService.add({ + severity: 'error', + summary: 'Serverfehler', + detail: + 'Beim Abrufen der Einträge ist ein Fehler aufgetreten. Bitte versuche es erneut.', + }); + break; + default: + console.error(error); + this.messageService.add({ + severity: 'error', + summary: 'Unerwarteter Fehler', + detail: + 'Beim Abrufen der Einträge hat der Server eine unerwartete Antwort zurückgegeben.', + }); + break; + } + + return EMPTY; + } +} diff --git a/src/Vegasco-Web/src/app/modules/cars/cars/components/car-card/car-card.component.html b/src/Vegasco-Web/src/app/modules/cars/cars/components/car-card/car-card.component.html new file mode 100644 index 0000000..671d678 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/cars/cars/components/car-card/car-card.component.html @@ -0,0 +1,22 @@ + + +
+
+
+ +
+
+ +
{{ car().name }}
+
+
+
+
+
+ +
+
\ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/cars/cars/components/car-card/car-card.component.scss b/src/Vegasco-Web/src/app/modules/cars/cars/components/car-card/car-card.component.scss new file mode 100644 index 0000000..42ece6f --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/cars/cars/components/car-card/car-card.component.scss @@ -0,0 +1,3 @@ +.edit-button { + cursor: pointer; +} diff --git a/src/Vegasco-Web/src/app/modules/cars/cars/components/car-card/car-card.component.ts b/src/Vegasco-Web/src/app/modules/cars/cars/components/car-card/car-card.component.ts new file mode 100644 index 0000000..8170817 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/cars/cars/components/car-card/car-card.component.ts @@ -0,0 +1,116 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, DestroyRef, inject, input, output } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { NgIconComponent, provideIcons } from '@ng-icons/core'; +import { + matDirectionsCarOutline, +} from '@ng-icons/material-icons/outline'; +import { + matDeleteSharp +} from '@ng-icons/material-icons/sharp'; +import { CarClient } from '@vegasco-web/api/cars/car-client'; +import { RoutingService } from '@vegasco-web/services/routing.service'; +import { ConfirmationService, MessageService } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; +import { CardModule } from 'primeng/card'; +import { ConfirmDialogModule } from 'primeng/confirmdialog'; +import { catchError, EMPTY, Observable, tap, throwError } from 'rxjs'; + +@Component({ + selector: 'app-car-card', + imports: [ + ButtonModule, + CardModule, + ConfirmDialogModule, + NgIconComponent, + ], + providers: [ + provideIcons({ + matDeleteSharp, + matDirectionsCarOutline, + }), + ConfirmationService, + ], + templateUrl: './car-card.component.html', + styleUrl: './car-card.component.scss' +}) +export class CarCardComponent { + readonly car = input.required(); + + readonly carDeleted = output(); + + private readonly routingService = inject(RoutingService); + private readonly carClient = inject(CarClient); + private readonly messageService = inject(MessageService); + private readonly confirmationService = inject(ConfirmationService); + + private readonly destroyRef = inject(DestroyRef); + + async navigateToEdit(): Promise { + await this.routingService.navigateToEditCar(this.car().id); + } + + confirmDeleteCar(): void { + this.confirmationService.confirm({ + closeOnEscape: true, + dismissableMask: true, + header: 'Bist du sicher?', + message: `Möchtest du das Auto "${this.car().name}" wirklich löschen?`, + acceptButtonProps: { + label: 'Löschen', + severity: 'danger', + }, + rejectButtonProps: { + label: 'Abbrechen', + outlined: true, + }, + accept: () => this.deleteCar(), + }); + } + + deleteCar(): void { + this.carClient.delete(this.car().id) + .pipe( + takeUntilDestroyed(this.destroyRef), + tap(() => this.carDeleted.emit(this.car())), + catchError((error) => this.handleError(error)), + ) + .subscribe(); + } + + private handleError(error: unknown): Observable { + if (!(error instanceof HttpErrorResponse)) { + return throwError(() => error); + } + + switch (true) { + case error.status >= 500 && error.status <= 599: + this.messageService.add({ + severity: 'error', + summary: 'Serverfehler', + detail: + 'Beim Löschen des Autos ist ein Fehler aufgetreten. Bitte versuche es erneut.', + }); + break; + case error.status === 400: + this.messageService.add({ + severity: 'error', + summary: 'Clientfehler', + detail: + 'Die Anwendung scheint falsche Daten an den Server zu senden.', + }); + break; + default: + console.error(error); + this.messageService.add({ + severity: 'error', + summary: 'Unerwarteter Fehler', + detail: + 'Beim Löschen des Autos hat der Server eine unerwartete Antwort zurückgegeben.', + }); + break; + } + + return EMPTY; + } +} diff --git a/src/Vegasco-Web/src/app/modules/cars/edit-car/components/required-marker.component.html b/src/Vegasco-Web/src/app/modules/cars/edit-car/components/required-marker.component.html new file mode 100644 index 0000000..a01d1a4 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/cars/edit-car/components/required-marker.component.html @@ -0,0 +1 @@ +* diff --git a/src/Vegasco-Web/src/app/modules/cars/edit-car/components/required-marker.component.scss b/src/Vegasco-Web/src/app/modules/cars/edit-car/components/required-marker.component.scss new file mode 100644 index 0000000..00a2ac1 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/cars/edit-car/components/required-marker.component.scss @@ -0,0 +1,3 @@ +.required { + color: red; +} \ No newline at end of file diff --git a/src/Vegasco-Web/src/app/modules/cars/edit-car/components/required-marker.component.ts b/src/Vegasco-Web/src/app/modules/cars/edit-car/components/required-marker.component.ts new file mode 100644 index 0000000..5017a19 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/cars/edit-car/components/required-marker.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-required-marker', + imports: [ + ], + templateUrl: './required-marker.component.html', + styleUrl: './required-marker.component.scss' +}) +export class RequiredMarkerComponent { +} diff --git a/src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.html b/src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.html new file mode 100644 index 0000000..8f3d4c4 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.html @@ -0,0 +1,31 @@ +@if (!isCarDataLoaded()) { +
+ +
+ + +
+
+} @else { + + +
+ + +
+ +
+ + +
+ + +} diff --git a/src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.scss b/src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.ts b/src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.ts new file mode 100644 index 0000000..01b0f52 --- /dev/null +++ b/src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.ts @@ -0,0 +1,217 @@ +import { HttpErrorResponse } from '@angular/common/http'; +import { Component, DestroyRef, inject, input, OnInit, signal } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { CarClient } from '@vegasco-web/api/cars/car-client'; +import { RoutingService } from '@vegasco-web/services/routing.service'; +import { MessageService } from 'primeng/api'; +import { ButtonModule } from 'primeng/button'; +import { ChipModule } from 'primeng/chip'; +import { DatePickerModule } from 'primeng/datepicker'; +import { FloatLabelModule } from 'primeng/floatlabel'; +import { InputGroupModule } from 'primeng/inputgroup'; +import { InputGroupAddonModule } from 'primeng/inputgroupaddon'; +import { InputNumberModule } from 'primeng/inputnumber'; +import { InputTextModule } from 'primeng/inputtext'; +import { MultiSelectModule } from 'primeng/multiselect'; +import { SelectModule } from 'primeng/select'; +import { SkeletonModule } from 'primeng/skeleton'; +import { catchError, EMPTY, Observable, switchMap, tap, throwError } from 'rxjs'; +import { RequiredMarkerComponent } from './components/required-marker.component'; + +@Component({ + selector: 'app-edit-entry', + imports: [ + ButtonModule, + ChipModule, + DatePickerModule, + FloatLabelModule, + InputGroupAddonModule, + InputGroupModule, + InputNumberModule, + InputTextModule, + MultiSelectModule, + ReactiveFormsModule, + RequiredMarkerComponent, + SelectModule, + SkeletonModule, + ], + templateUrl: './edit-car.component.html', + styleUrl: './edit-car.component.scss' +}) +export class EditCarComponent implements OnInit { + private readonly carClient = inject(CarClient); + private readonly routingService = inject(RoutingService); + private readonly destroyRef = inject(DestroyRef); + private readonly messageService = inject(MessageService); + + protected readonly id = input(undefined); + + protected readonly today = new Date(); + + protected readonly formFieldNames = { + name: 'name', + } as const; + + protected readonly formGroup = new FormGroup({ + [this.formFieldNames.name]: new FormControl({ value: null, disabled: true }, [Validators.required]), + }); + + protected readonly isCarDataLoaded = signal(false); + + ngOnInit(): void { + this.loadEntryDetailsAndEnableControls(); + } + + private loadEntryDetailsAndEnableControls() { + const carId = this.id(); + + if (carId === undefined || carId === null) { + this.enableFormControls(); + this.isCarDataLoaded.set(true); + return; + } + + this.carClient + .getSingle(carId) + .pipe( + takeUntilDestroyed(this.destroyRef), + catchError((error) => this.handleGetError(error)), + tap((car) => { + this.formGroup.patchValue({ + [this.formFieldNames.name]: car.name, + }); + }), + tap(() => { + this.enableFormControls(); + this.isCarDataLoaded.set(true); + }), + ) + .subscribe(); + + } + + private enableFormControls(): void { + for (const controlName of Object.values(this.formFieldNames)) { + const control = this.formGroup.get(controlName); + if (control) { + control.enable(); + } else { + console.warn(`Form control '${controlName}' not found.`); + } + } + } + + async navigateToOverviewPage(): Promise { + await this.routingService.navigateToCars(); + } + + onSubmit(): void { + if (this.formGroup.invalid) { + this.formGroup.markAllAsTouched(); + return; + } + + var carId = this.id(); + if (carId === undefined || carId === null) { + this.createCar(); + return; + } + + this.updateCar(carId); + } + + private getFormData() { + return { + name: this.formGroup.controls[this.formFieldNames.name].value!, + }; + } + + createCar() { + var request: CreateCarRequest = this.getFormData(); + this.carClient.create(request) + .pipe( + takeUntilDestroyed(this.destroyRef), + catchError((error) => this.handleCreateOrUpdateError(error, false)), + switchMap(() => this.routingService.navigateToCars()) + ) + .subscribe(); + } + + updateCar(id: string) { + var request: UpdateCarRequest = this.getFormData(); + this.carClient.update(id, request) + .pipe( + takeUntilDestroyed(this.destroyRef), + catchError((error) => this.handleCreateOrUpdateError(error, true)), + switchMap(() => this.routingService.navigateToCars()) + ) + .subscribe(); + } + + private handleGetError(error: unknown): Observable { + if (!(error instanceof HttpErrorResponse)) { + return throwError(() => error); + } + + switch (true) { + case error.status >= 500 && error.status <= 599: + this.messageService.add({ + severity: 'error', + summary: 'Serverfehler', + detail: + 'Beim Abrufen des Autos ist ein Fehler aufgetreten. Bitte versuche es erneut.', + }); + break; + default: + console.error(error); + this.messageService.add({ + severity: 'error', + summary: 'Unerwarteter Fehler', + detail: + 'Beim Abrufen des Autos hat der Server eine unerwartete Antwort zurückgegeben.', + }); + break; + } + + return EMPTY; + } + + private handleCreateOrUpdateError(error: unknown, isUpdate: boolean): Observable { + if (!(error instanceof HttpErrorResponse)) { + return throwError(() => error); + } + + const action = isUpdate ? 'Aktualisieren' : 'Erstellen'; + + switch (true) { + case error.status >= 500 && error.status <= 599: + this.messageService.add({ + severity: 'error', + summary: 'Serverfehler', + detail: + `Beim ${action} des Eintrags ist ein Fehler aufgetreten. Bitte versuche es erneut.`, + }); + break; + case error.status === 400: + this.messageService.add({ + severity: 'error', + summary: 'Clientfehler', + detail: + 'Die Anwendung scheint falsche Daten an den Server zu senden.', + }); + break; + default: + console.error(error); + this.messageService.add({ + severity: 'error', + summary: 'Unerwarteter Fehler', + detail: + `Beim ${action} des Eintrags hat der Server eine unerwartete Antwort zurückgegeben.`, + }); + break; + } + + return EMPTY; + } +} diff --git a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts index 3cde557..7a8cb4a 100644 --- a/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/edit-entry/edit-entry.component.ts @@ -169,6 +169,11 @@ export class EditEntryComponent implements OnInit { } onSubmit(): void { + if (this.formGroup.invalid) { + this.formGroup.markAllAsTouched(); + return; + } + var entryId = this.id(); if (entryId === undefined || entryId === null) { this.createEntry(); diff --git a/src/Vegasco-Web/src/app/services/routing.service.ts b/src/Vegasco-Web/src/app/services/routing.service.ts index 2c4ae34..bc6a961 100644 --- a/src/Vegasco-Web/src/app/services/routing.service.ts +++ b/src/Vegasco-Web/src/app/services/routing.service.ts @@ -18,4 +18,16 @@ export class RoutingService { async navigateToCreateEntry(): Promise { await this.router.navigate(['entries', 'create']); } + + async navigateToCars(): Promise { + await this.router.navigateByUrl('/cars'); + } + + async navigateToEditCar(entryId: string): Promise { + await this.router.navigate(['cars', 'edit', entryId]); + } + + async navigateToCreateCar(): Promise { + await this.router.navigate(['cars', 'create']); + } } \ No newline at end of file -- 2.49.1 From 7f61e011ed6022826f33639c17697ad5c834c9e8 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 23 Jun 2025 16:49:37 +0200 Subject: [PATCH 132/150] Add car name duplicate validation --- .../modules/cars/edit-car/edit-car.component.ts | 8 ++++++++ src/Vegasco.Server.Api/Cars/CreateCar.cs | 14 ++++++++++++-- src/Vegasco.Server.Api/Cars/UpdateCar.cs | 14 ++++++++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.ts b/src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.ts index 01b0f52..3185433 100644 --- a/src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.ts +++ b/src/Vegasco-Web/src/app/modules/cars/edit-car/edit-car.component.ts @@ -201,6 +201,14 @@ export class EditCarComponent implements OnInit { 'Die Anwendung scheint falsche Daten an den Server zu senden.', }); break; + case error.status === 409: + this.messageService.add({ + severity: 'warn', + summary: 'Konflikt', + detail: + 'Es existiert bereits ein Auto mit diesem Namen. Bitte wähle einen anderen Namen.', + }); + break; default: console.error(error); this.messageService.add({ diff --git a/src/Vegasco.Server.Api/Cars/CreateCar.cs b/src/Vegasco.Server.Api/Cars/CreateCar.cs index ab453ed..d87efe5 100644 --- a/src/Vegasco.Server.Api/Cars/CreateCar.cs +++ b/src/Vegasco.Server.Api/Cars/CreateCar.cs @@ -1,5 +1,6 @@ using FluentValidation; using FluentValidation.Results; +using Microsoft.EntityFrameworkCore; using Vegasco.Server.Api.Authentication; using Vegasco.Server.Api.Common; using Vegasco.Server.Api.Persistence; @@ -19,7 +20,8 @@ public static class CreateCar .WithTags("Cars") .WithDescription("Creates a new car") .Produces(201) - .ProducesValidationProblem(); + .ProducesValidationProblem() + .Produces(409); } public class Validator : AbstractValidator @@ -59,10 +61,18 @@ public static class CreateCar Car car = new() { - Name = request.Name, + Name = request.Name.Trim(), UserId = userId }; + var isDuplicate = await dbContext.Cars + .AnyAsync(x => x.Name.ToUpper() == request.Name.ToUpper(), cancellationToken); + + if (isDuplicate) + { + return TypedResults.Conflict(); + } + await dbContext.Cars.AddAsync(car, cancellationToken); await dbContext.SaveChangesAsync(cancellationToken); diff --git a/src/Vegasco.Server.Api/Cars/UpdateCar.cs b/src/Vegasco.Server.Api/Cars/UpdateCar.cs index 9d13ed8..055f509 100644 --- a/src/Vegasco.Server.Api/Cars/UpdateCar.cs +++ b/src/Vegasco.Server.Api/Cars/UpdateCar.cs @@ -1,5 +1,6 @@ using FluentValidation; using FluentValidation.Results; +using Microsoft.EntityFrameworkCore; using Vegasco.Server.Api.Authentication; using Vegasco.Server.Api.Common; using Vegasco.Server.Api.Persistence; @@ -19,7 +20,8 @@ public static class UpdateCar .WithDescription("Updates a car by ID") .Produces() .ProducesValidationProblem() - .Produces(404); + .Produces(404) + .Produces(409); } public class Validator : AbstractValidator @@ -53,7 +55,15 @@ public static class UpdateCar return TypedResults.NotFound(); } - car.Name = request.Name; + var isDuplicate = await dbContext.Cars + .AnyAsync(x => x.Name.ToUpper() == request.Name.ToUpper(), cancellationToken); + + if (isDuplicate) + { + return TypedResults.Conflict(); + } + + car.Name = request.Name.Trim(); await dbContext.SaveChangesAsync(cancellationToken); Response response = new(car.Id.Value, car.Name); -- 2.49.1 From 0df7449a9962ed981e481d45b0710ff6d6c8957a Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 23 Jun 2025 16:49:52 +0200 Subject: [PATCH 133/150] Add pgweb and pgadmin in development env --- src/Vegasco.Server.AppHost/Program.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Vegasco.Server.AppHost/Program.cs b/src/Vegasco.Server.AppHost/Program.cs index 7d36ee5..c4debdf 100644 --- a/src/Vegasco.Server.AppHost/Program.cs +++ b/src/Vegasco.Server.AppHost/Program.cs @@ -1,10 +1,20 @@ +using Microsoft.Extensions.Hosting; using Vegasco.Server.AppHost.Shared; IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder(args); -IResourceBuilder postgres = builder.AddPostgres(Constants.Database.ServiceName) +IResourceBuilder postgresBuilder = builder.AddPostgres(Constants.Database.ServiceName) .WithLifetime(ContainerLifetime.Persistent) - .WithDataVolume() + .WithDataVolume(); + +if (builder.Environment.IsDevelopment()) +{ + postgresBuilder = postgresBuilder + .WithPgWeb() + .WithPgAdmin(); +} + +IResourceBuilder postgres = postgresBuilder .AddDatabase(Constants.Database.Name); IResourceBuilder api = builder -- 2.49.1 From 9e16d6004a2d79ffb2b37660b27e6ef47408a752 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 23 Jun 2025 16:50:07 +0200 Subject: [PATCH 134/150] Fix liter per 100 km calculation for multiple cars --- .../Consumptions/GetConsumptions.cs | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs index 6f59411..84d63a6 100644 --- a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs @@ -51,34 +51,38 @@ public static class GetConsumptions ApplicationDbContext dbContext, CancellationToken cancellationToken) { - List consumptions = await dbContext.Consumptions + Dictionary> consumptionsByCar = await dbContext.Consumptions .OrderByDescending(x => x.DateTime) .Include(x => x.Car) - .ToListAsync(cancellationToken); + .GroupBy(x => x.CarId) + .ToDictionaryAsync(x => x.Key, x => x.ToList(), cancellationToken); List responses = []; - for (int i = 0; i < consumptions.Count; i++) + foreach (var consumptions in consumptionsByCar.Select(x => x.Value)) { - Consumption consumption = consumptions[i]; - - double? literPer100Km = null; - - bool isLast = i == consumptions.Count - 1; - if (!isLast) + for (int i = 0; i < consumptions.Count; i++) { - Consumption previousConsumption = consumptions[i + 1]; - double distanceDiff = consumption.Distance - previousConsumption.Distance; - literPer100Km = consumption.Amount / (distanceDiff / 100); - } + Consumption consumption = consumptions[i]; - responses.Add(new ResponseDto( - consumption.Id.Value, - consumption.DateTime, - consumption.Distance, - consumption.Amount, - CarDto.FromCar(consumption.Car), - literPer100Km)); + double? literPer100Km = null; + + bool isLast = i == consumptions.Count - 1; + if (!isLast) + { + Consumption previousConsumption = consumptions[i + 1]; + double distanceDiff = consumption.Distance - previousConsumption.Distance; + literPer100Km = consumption.Amount / (distanceDiff / 100); + } + + responses.Add(new ResponseDto( + consumption.Id.Value, + consumption.DateTime, + consumption.Distance, + consumption.Amount, + CarDto.FromCar(consumption.Car), + literPer100Km)); + } } ApiResponse apiResponse = new() { Consumptions = responses }; -- 2.49.1 From eaa06029bb27dbd6767f0412cfa378f7b3655cc5 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 23 Jun 2025 16:53:11 +0200 Subject: [PATCH 135/150] Reset selected car if it is deleted --- .../src/app/modules/cars/cars/cars.component.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/cars/cars/cars.component.ts b/src/Vegasco-Web/src/app/modules/cars/cars/cars.component.ts index c6d1da2..1024907 100644 --- a/src/Vegasco-Web/src/app/modules/cars/cars/cars.component.ts +++ b/src/Vegasco-Web/src/app/modules/cars/cars/cars.component.ts @@ -25,6 +25,7 @@ import { throwError } from 'rxjs'; import { CarCardComponent } from './components/car-card/car-card.component'; +import { SelectedCarService } from '@vegasco-web/modules/entries/services/selected-car.service'; @Component({ selector: 'app-entries', @@ -52,6 +53,7 @@ import { CarCardComponent } from './components/car-card/car-card.component'; export class CarsComponent { private readonly carClient = inject(CarClient); private readonly messageService = inject(MessageService); + private readonly selectedCarService = inject(SelectedCarService); protected readonly nonDeletedCars$: Observable; @@ -78,13 +80,21 @@ export class CarsComponent { ); } - onCarDeleted(entry: Car): void { - this.deletedCars$.next([...this.deletedCars$.value, entry.id]); + onCarDeleted(car: Car): void { + this.deletedCars$.next([...this.deletedCars$.value, car.id]); this.messageService.add({ severity: 'success', summary: 'Auto gelöscht', detail: 'Das Auto wurde erfolgreich gelöscht.', }); + this.resetSelectedCarIfDeleted(car); + } + + private resetSelectedCarIfDeleted(car: Car) { + const selectedCarId = this.selectedCarService.getSelectedCarId(); + if (selectedCarId === car.id) { + this.selectedCarService.setSelectedCarId(null); + } } private handleGetCarsError(error: unknown): Observable { -- 2.49.1 From f6dbf489adb69b7b548c987690e31cac688b168d Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Mon, 23 Jun 2025 18:38:26 +0200 Subject: [PATCH 136/150] Dockerize web app --- src/Vegasco-Web/.dockerignore | 10 ++++++++++ src/Vegasco-Web/Dockerfile | 13 +++++++++++++ src/Vegasco-Web/angular.json | 2 +- src/Vegasco-Web/nginx.conf | 13 +++++++++++++ src/Vegasco-Web/package.json | 4 +++- 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 src/Vegasco-Web/.dockerignore create mode 100644 src/Vegasco-Web/Dockerfile create mode 100644 src/Vegasco-Web/nginx.conf diff --git a/src/Vegasco-Web/.dockerignore b/src/Vegasco-Web/.dockerignore new file mode 100644 index 0000000..6b38648 --- /dev/null +++ b/src/Vegasco-Web/.dockerignore @@ -0,0 +1,10 @@ +node_modules +npm-debug.log +Dockerfile* +docker-compose* +.dockerignore +.git +.gitignore +README.md +LICENSE +.vscode diff --git a/src/Vegasco-Web/Dockerfile b/src/Vegasco-Web/Dockerfile new file mode 100644 index 0000000..578e7eb --- /dev/null +++ b/src/Vegasco-Web/Dockerfile @@ -0,0 +1,13 @@ +FROM node:lts AS build +RUN npm install -g pnpm +ARG CONFIGURATION=development +WORKDIR /usr/local/app +COPY . . +RUN pnpm install +RUN pnpm "build:$CONFIGURATION" + +FROM nginx:alpine +WORKDIR /usr/share/nginx/html +COPY --from=build /usr/local/app/dist/Vegasco-Web/browser . +COPY nginx.conf /etc/nginx/nginx.conf +EXPOSE 80 diff --git a/src/Vegasco-Web/angular.json b/src/Vegasco-Web/angular.json index 5e6e817..776e5f4 100644 --- a/src/Vegasco-Web/angular.json +++ b/src/Vegasco-Web/angular.json @@ -17,7 +17,7 @@ "build": { "builder": "@angular-devkit/build-angular:application", "options": { - "outputPath": "dist/tmp", + "outputPath": "dist/Vegasco-Web", "index": "src/index.html", "browser": "src/main.ts", "polyfills": [ diff --git a/src/Vegasco-Web/nginx.conf b/src/Vegasco-Web/nginx.conf new file mode 100644 index 0000000..340e459 --- /dev/null +++ b/src/Vegasco-Web/nginx.conf @@ -0,0 +1,13 @@ +events { } +http { + include mime.types; + + server { + listen 80; + + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html =404; + } + } +} diff --git a/src/Vegasco-Web/package.json b/src/Vegasco-Web/package.json index 9a18012..8ab96ff 100644 --- a/src/Vegasco-Web/package.json +++ b/src/Vegasco-Web/package.json @@ -7,7 +7,9 @@ "start": "run-script-os", "start:win32": "ng serve --port %PORT% --configuration development", "start:default": "ng serve --port $PORT --configuration development", - "build": "ng build", + "build": "pnpm build:development", + "build:development": "ng build", + "build:production": "ng build --configuration production", "watch": "ng build --watch --configuration development", "test": "ng test" }, -- 2.49.1 From 8681247e76fbb8637ed1b993a426192fe955952f Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Tue, 24 Jun 2025 19:12:29 +0200 Subject: [PATCH 137/150] Adjustments for working deployment --- src/Vegasco-Web/Dockerfile | 5 +++++ src/Vegasco-Web/nginx.conf | 9 ++------- src/Vegasco-Web/webserver.conf.template | 12 ++++++++++++ src/Vegasco.Server.AppHost.Shared/Constants.cs | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 src/Vegasco-Web/webserver.conf.template diff --git a/src/Vegasco-Web/Dockerfile b/src/Vegasco-Web/Dockerfile index 578e7eb..c3bdda0 100644 --- a/src/Vegasco-Web/Dockerfile +++ b/src/Vegasco-Web/Dockerfile @@ -7,7 +7,12 @@ RUN pnpm install RUN pnpm "build:$CONFIGURATION" FROM nginx:alpine +RUN rm /etc/nginx/conf.d/* + +ENV DOLLAR=$ + WORKDIR /usr/share/nginx/html COPY --from=build /usr/local/app/dist/Vegasco-Web/browser . COPY nginx.conf /etc/nginx/nginx.conf +COPY webserver.conf.template /etc/nginx/templates/webserver.conf.template EXPOSE 80 diff --git a/src/Vegasco-Web/nginx.conf b/src/Vegasco-Web/nginx.conf index 340e459..0cd7817 100644 --- a/src/Vegasco-Web/nginx.conf +++ b/src/Vegasco-Web/nginx.conf @@ -2,12 +2,7 @@ events { } http { include mime.types; - server { - listen 80; + resolver 8.8.8.8 8.8.4.4 1.1.1.1; - location / { - root /usr/share/nginx/html; - try_files $uri $uri/ /index.html =404; - } - } + include /etc/nginx/conf.d/webserver.conf; } diff --git a/src/Vegasco-Web/webserver.conf.template b/src/Vegasco-Web/webserver.conf.template new file mode 100644 index 0000000..b4c7d35 --- /dev/null +++ b/src/Vegasco-Web/webserver.conf.template @@ -0,0 +1,12 @@ +server { + listen 80; + + location ~ ^/api/(.*) { + proxy_pass ${services__Vegasco_Server_Api__https__0}/$1; + } + + location / { + root /usr/share/nginx/html; + try_files ${DOLLAR}uri ${DOLLAR}uri/ /index.html =404; + } +} \ No newline at end of file diff --git a/src/Vegasco.Server.AppHost.Shared/Constants.cs b/src/Vegasco.Server.AppHost.Shared/Constants.cs index b220976..9c1dc4c 100644 --- a/src/Vegasco.Server.AppHost.Shared/Constants.cs +++ b/src/Vegasco.Server.AppHost.Shared/Constants.cs @@ -4,7 +4,7 @@ public static class Constants { public static class Projects { - public const string Api = "Vegasco-Server-Api"; + public const string Api = "Vegasco_Server_Api"; } public static class Database -- 2.49.1 From ab32be98a63e2a0dcd014b7ca8dfb1ba90e24537 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Tue, 24 Jun 2025 19:28:55 +0200 Subject: [PATCH 138/150] Use concrete types --- src/Vegasco.Server.Api/Cars/CreateCar.cs | 2 +- src/Vegasco.Server.Api/Cars/DeleteCar.cs | 4 ++-- src/Vegasco.Server.Api/Cars/GetCar.cs | 2 +- src/Vegasco.Server.Api/Cars/GetCars.cs | 2 +- src/Vegasco.Server.Api/Cars/UpdateCar.cs | 2 +- .../Common/DependencyInjectionExtensions.cs | 2 +- .../Consumptions/DeleteConsumptions.cs | 4 ++-- .../Consumptions/GetConsumptions.cs | 2 +- .../Persistence/ApplyMigrationsService.cs | 4 ++-- .../Cars/CreateCarTests.cs | 6 +++--- .../Cars/DeleteCarTests.cs | 4 ++-- .../Cars/GetCarTests.cs | 6 +++--- .../Cars/GetCarsTests.cs | 8 ++++---- .../Cars/UpdateCarTests.cs | 12 ++++++------ .../Consumptions/CreateConsumptionTests.cs | 6 +++--- .../Consumptions/DeleteConsumptionTests.cs | 6 +++--- .../Consumptions/GetConsumptionTests.cs | 8 ++++---- .../Consumptions/UpdateConsumptionTests.cs | 12 ++++++------ .../Info/GetServerInfoTests.cs | 2 +- .../PostgresRespawner.cs | 2 +- 20 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/Vegasco.Server.Api/Cars/CreateCar.cs b/src/Vegasco.Server.Api/Cars/CreateCar.cs index d87efe5..8f7ef96 100644 --- a/src/Vegasco.Server.Api/Cars/CreateCar.cs +++ b/src/Vegasco.Server.Api/Cars/CreateCar.cs @@ -65,7 +65,7 @@ public static class CreateCar UserId = userId }; - var isDuplicate = await dbContext.Cars + bool isDuplicate = await dbContext.Cars .AnyAsync(x => x.Name.ToUpper() == request.Name.ToUpper(), cancellationToken); if (isDuplicate) diff --git a/src/Vegasco.Server.Api/Cars/DeleteCar.cs b/src/Vegasco.Server.Api/Cars/DeleteCar.cs index a6d0aab..853c665 100644 --- a/src/Vegasco.Server.Api/Cars/DeleteCar.cs +++ b/src/Vegasco.Server.Api/Cars/DeleteCar.cs @@ -21,7 +21,7 @@ public static class DeleteCar ILoggerFactory loggerFactory, CancellationToken cancellationToken) { - var rows = await dbContext.Cars + int rows = await dbContext.Cars .Where(x => x.Id == new CarId(id)) .ExecuteDeleteAsync(cancellationToken); @@ -32,7 +32,7 @@ public static class DeleteCar if (rows > 1) { - var logger = loggerFactory.CreateLogger(nameof(DeleteCar)); + ILogger logger = loggerFactory.CreateLogger(nameof(DeleteCar)); logger.LogWarning("Deleted '{DeletedRowCount}' rows for id '{CarId}'", rows, id); } diff --git a/src/Vegasco.Server.Api/Cars/GetCar.cs b/src/Vegasco.Server.Api/Cars/GetCar.cs index c4b401c..b9fc687 100644 --- a/src/Vegasco.Server.Api/Cars/GetCar.cs +++ b/src/Vegasco.Server.Api/Cars/GetCar.cs @@ -29,7 +29,7 @@ public static class GetCar return TypedResults.NotFound(); } - var response = new Response(car.Id.Value, car.Name); + Response response = new Response(car.Id.Value, car.Name); return TypedResults.Ok(response); } } \ No newline at end of file diff --git a/src/Vegasco.Server.Api/Cars/GetCars.cs b/src/Vegasco.Server.Api/Cars/GetCars.cs index 9f9f096..5a1ed2a 100644 --- a/src/Vegasco.Server.Api/Cars/GetCars.cs +++ b/src/Vegasco.Server.Api/Cars/GetCars.cs @@ -38,7 +38,7 @@ public static class GetCars .Select(x => new ResponseDto(x.Id.Value, x.Name)) .ToListAsync(cancellationToken); - var response = new ApiResponse + ApiResponse response = new ApiResponse { Cars = cars }; diff --git a/src/Vegasco.Server.Api/Cars/UpdateCar.cs b/src/Vegasco.Server.Api/Cars/UpdateCar.cs index 055f509..ff97f20 100644 --- a/src/Vegasco.Server.Api/Cars/UpdateCar.cs +++ b/src/Vegasco.Server.Api/Cars/UpdateCar.cs @@ -55,7 +55,7 @@ public static class UpdateCar return TypedResults.NotFound(); } - var isDuplicate = await dbContext.Cars + bool isDuplicate = await dbContext.Cars .AnyAsync(x => x.Name.ToUpper() == request.Name.ToUpper(), cancellationToken); if (isDuplicate) diff --git a/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs b/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs index 44faa51..ccdbd30 100644 --- a/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs +++ b/src/Vegasco.Server.Api/Common/DependencyInjectionExtensions.cs @@ -121,7 +121,7 @@ public static class DependencyInjectionExtensions .ValidateFluently() .ValidateOnStart(); - var jwtOptions = services.BuildServiceProvider().GetRequiredService>(); + IOptions jwtOptions = services.BuildServiceProvider().GetRequiredService>(); services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o => diff --git a/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs index 774741b..48dae75 100644 --- a/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/DeleteConsumptions.cs @@ -21,7 +21,7 @@ public static class DeleteConsumption ILoggerFactory loggerFactory, CancellationToken cancellationToken) { - var rows = await dbContext.Consumptions + int rows = await dbContext.Consumptions .Where(x => x.Id == new ConsumptionId(id)) .ExecuteDeleteAsync(cancellationToken); @@ -32,7 +32,7 @@ public static class DeleteConsumption if (rows > 1) { - var logger = loggerFactory.CreateLogger(nameof(DeleteConsumption)); + ILogger logger = loggerFactory.CreateLogger(nameof(DeleteConsumption)); logger.LogWarning("Deleted '{DeletedRowCount}' rows for id '{ConsumptionId}'", rows, id); } diff --git a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs index 84d63a6..b440142 100644 --- a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs @@ -59,7 +59,7 @@ public static class GetConsumptions List responses = []; - foreach (var consumptions in consumptionsByCar.Select(x => x.Value)) + foreach (List consumptions in consumptionsByCar.Select(x => x.Value)) { for (int i = 0; i < consumptions.Count; i++) { diff --git a/src/Vegasco.Server.Api/Persistence/ApplyMigrationsService.cs b/src/Vegasco.Server.Api/Persistence/ApplyMigrationsService.cs index 3b0ffc7..6fd5afb 100644 --- a/src/Vegasco.Server.Api/Persistence/ApplyMigrationsService.cs +++ b/src/Vegasco.Server.Api/Persistence/ApplyMigrationsService.cs @@ -11,12 +11,12 @@ public class ApplyMigrationsService( { public async Task StartAsync(CancellationToken cancellationToken) { - using var activity = activitySource.StartActivity("ApplyMigrations"); + using Activity? activity = activitySource.StartActivity("ApplyMigrations"); logger.LogInformation("Starting migrations"); using IServiceScope scope = scopeFactory.CreateScope(); - await using var dbContext = scope.ServiceProvider.GetRequiredService(); + await using ApplicationDbContext dbContext = scope.ServiceProvider.GetRequiredService(); await dbContext.Database.MigrateAsync(cancellationToken); } diff --git a/tests/Vegasco.Server.Api.Tests.Integration/Cars/CreateCarTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Cars/CreateCarTests.cs index 28d3dd5..95c076c 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/Cars/CreateCarTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Cars/CreateCarTests.cs @@ -35,7 +35,7 @@ public class CreateCarTests : IAsyncLifetime // Assert response.StatusCode.Should().Be(HttpStatusCode.Created); - var createdCar = await response.Content.ReadFromJsonAsync(); + CreateCar.Response? createdCar = await response.Content.ReadFromJsonAsync(); createdCar.Should().BeEquivalentTo(createCarRequest, o => o.ExcludingMissingMembers()); _dbContext.Cars.Should().ContainEquivalentOf(createdCar, o => o.Excluding(x => x!.Id)) @@ -46,14 +46,14 @@ public class CreateCarTests : IAsyncLifetime public async Task CreateCar_ShouldReturnValidationProblems_WhenRequestIsNotValid() { // Arrange - var createCarRequest = new CreateCar.Request(""); + CreateCar.Request createCarRequest = new CreateCar.Request(""); // Act HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - var validationProblemDetails = await response.Content.ReadFromJsonAsync(); + ValidationProblemDetails? validationProblemDetails = await response.Content.ReadFromJsonAsync(); validationProblemDetails!.Errors.Keys.Should().Contain(x => x.Equals(nameof(CreateCar.Request.Name), StringComparison.OrdinalIgnoreCase)); diff --git a/tests/Vegasco.Server.Api.Tests.Integration/Cars/DeleteCarTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Cars/DeleteCarTests.cs index ba76a29..3348ded 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/Cars/DeleteCarTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Cars/DeleteCarTests.cs @@ -27,7 +27,7 @@ public class DeleteCarTests : IAsyncLifetime public async Task DeleteCar_ShouldReturnNotFound_WhenCarDoesNotExist() { // Arrange - var randomCarId = Guid.NewGuid(); + Guid randomCarId = Guid.NewGuid(); // Act HttpResponseMessage response = await _factory.HttpClient.DeleteAsync($"v1/cars/{randomCarId}"); @@ -43,7 +43,7 @@ public class DeleteCarTests : IAsyncLifetime CreateCar.Request createCarRequest = _carFaker.CreateCarRequest(); HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); - var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); + CreateCar.Response? createdCar = await createCarResponse.Content.ReadFromJsonAsync(); // Act HttpResponseMessage response = await _factory.HttpClient.DeleteAsync($"v1/cars/{createdCar!.Id}"); diff --git a/tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarTests.cs index 81ddb53..8a69239 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarTests.cs @@ -21,7 +21,7 @@ public class GetCarTests : IAsyncLifetime public async Task GetCar_ShouldReturnNotFound_WhenCarDoesNotExist() { // Arrange - var randomCarId = Guid.NewGuid(); + Guid randomCarId = Guid.NewGuid(); // Act HttpResponseMessage response = await _factory.HttpClient.GetAsync($"v1/cars/{randomCarId}"); @@ -37,14 +37,14 @@ public class GetCarTests : IAsyncLifetime CreateCar.Request createCarRequest = _carFaker.CreateCarRequest(); HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); - var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); + CreateCar.Response? createdCar = await createCarResponse.Content.ReadFromJsonAsync(); // Act HttpResponseMessage response = await _factory.HttpClient.GetAsync($"v1/cars/{createdCar!.Id}"); // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); - var car = await response.Content.ReadFromJsonAsync(); + GetCar.Response? car = await response.Content.ReadFromJsonAsync(); car.Should().BeEquivalentTo(createdCar); } diff --git a/tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarsTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarsTests.cs index 17a45dd..6ac7d57 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarsTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Cars/GetCarsTests.cs @@ -27,7 +27,7 @@ public class GetCarsTests : IAsyncLifetime // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); - var apiResponse = await response.Content.ReadFromJsonAsync(); + GetCars.ApiResponse? apiResponse = await response.Content.ReadFromJsonAsync(); apiResponse!.Cars.Should().BeEmpty(); } @@ -38,13 +38,13 @@ public class GetCarsTests : IAsyncLifetime List createdCars = []; const int numberOfCars = 5; - for (var i = 0; i < numberOfCars; i++) + for (int i = 0; i < numberOfCars; i++) { CreateCar.Request createCarRequest = _carFaker.CreateCarRequest(); HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); - var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); + CreateCar.Response? createdCar = await createCarResponse.Content.ReadFromJsonAsync(); createdCars.Add(createdCar!); } @@ -53,7 +53,7 @@ public class GetCarsTests : IAsyncLifetime // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); - var apiResponse = await response.Content.ReadFromJsonAsync(); + GetCars.ApiResponse? apiResponse = await response.Content.ReadFromJsonAsync(); apiResponse!.Cars.Should().BeEquivalentTo(createdCars); } diff --git a/tests/Vegasco.Server.Api.Tests.Integration/Cars/UpdateCarTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Cars/UpdateCarTests.cs index 3896669..f97ca4b 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/Cars/UpdateCarTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Cars/UpdateCarTests.cs @@ -31,7 +31,7 @@ public class UpdateCarTests : IAsyncLifetime CreateCar.Request createCarRequest = _carFaker.CreateCarRequest(); HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); - var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); + CreateCar.Response? createdCar = await createCarResponse.Content.ReadFromJsonAsync(); UpdateCar.Request updateCarRequest = _carFaker.UpdateCarRequest(); @@ -40,7 +40,7 @@ public class UpdateCarTests : IAsyncLifetime // Assert response.StatusCode.Should().Be(HttpStatusCode.OK); - var updatedCar = await response.Content.ReadFromJsonAsync(); + CreateCar.Response? updatedCar = await response.Content.ReadFromJsonAsync(); updatedCar!.Id.Should().Be(createdCar.Id); updatedCar.Should().BeEquivalentTo(updateCarRequest, o => o.ExcludingMissingMembers()); @@ -57,16 +57,16 @@ public class UpdateCarTests : IAsyncLifetime CreateCar.Request createCarRequest = _carFaker.CreateCarRequest(); HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); - var createdCar = await createCarResponse.Content.ReadFromJsonAsync(); + CreateCar.Response? createdCar = await createCarResponse.Content.ReadFromJsonAsync(); - var updateCarRequest = new UpdateCar.Request(""); + UpdateCar.Request updateCarRequest = new UpdateCar.Request(""); // Act HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{createdCar!.Id}", updateCarRequest); // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - var validationProblemDetails = await response.Content.ReadFromJsonAsync(); + ValidationProblemDetails? validationProblemDetails = await response.Content.ReadFromJsonAsync(); validationProblemDetails!.Errors.Keys.Should().Contain(x => x.Equals(nameof(CreateCar.Request.Name), StringComparison.OrdinalIgnoreCase)); @@ -80,7 +80,7 @@ public class UpdateCarTests : IAsyncLifetime { // Arrange UpdateCar.Request updateCarRequest = _carFaker.UpdateCarRequest(); - var randomCarId = Guid.NewGuid(); + Guid randomCarId = Guid.NewGuid(); // Act HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/cars/{randomCarId}", updateCarRequest); diff --git a/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/CreateConsumptionTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/CreateConsumptionTests.cs index 123b24b..6783011 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/CreateConsumptionTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/CreateConsumptionTests.cs @@ -39,7 +39,7 @@ public class CreateConsumptionTests : IAsyncLifetime // Assert response.StatusCode.Should().Be(HttpStatusCode.Created); - var createdConsumption = await response.Content.ReadFromJsonAsync(); + CreateConsumption.Response? createdConsumption = await response.Content.ReadFromJsonAsync(); createdConsumption.Should().BeEquivalentTo(createConsumptionRequest, o => o.ExcludingMissingMembers()); _dbContext.Consumptions.Should().HaveCount(1) @@ -64,7 +64,7 @@ public class CreateConsumptionTests : IAsyncLifetime // Assert response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - var validationProblemDetails = await response.Content.ReadFromJsonAsync(); + ValidationProblemDetails? validationProblemDetails = await response.Content.ReadFromJsonAsync(); validationProblemDetails!.Errors.Keys.Should().Contain(x => x.Equals(nameof(createConsumptionRequest.CarId), StringComparison.OrdinalIgnoreCase)); @@ -76,7 +76,7 @@ public class CreateConsumptionTests : IAsyncLifetime CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest(); using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); - var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); + CreateCar.Response? createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); return createdCarResponse!; } diff --git a/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/DeleteConsumptionTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/DeleteConsumptionTests.cs index 113109a..9091ccd 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/DeleteConsumptionTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/DeleteConsumptionTests.cs @@ -43,7 +43,7 @@ public class DeleteConsumptionTests : IAsyncLifetime public async Task DeleteConsumption_ShouldReturnNotFound_WhenConsumptionDoesNotExist() { // Arrange - var consumptionId = Guid.NewGuid(); + Guid consumptionId = Guid.NewGuid(); // Act using HttpResponseMessage response = await _factory.HttpClient.DeleteAsync($"v1/consumptions/{consumptionId}"); @@ -58,7 +58,7 @@ public class DeleteConsumptionTests : IAsyncLifetime CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id); using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest); response.EnsureSuccessStatusCode(); - var createdConsumption = await response.Content.ReadFromJsonAsync(); + CreateConsumption.Response? createdConsumption = await response.Content.ReadFromJsonAsync(); return createdConsumption!; } @@ -67,7 +67,7 @@ public class DeleteConsumptionTests : IAsyncLifetime CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest(); using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); - var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); + CreateCar.Response? createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); return createdCarResponse!; } diff --git a/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionTests.cs index 8596d73..14dd412 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/GetConsumptionTests.cs @@ -37,7 +37,7 @@ public class GetConsumptionTests : IAsyncLifetime // Assert string content = await response.Content.ReadAsStringAsync(); response.StatusCode.Should().Be(HttpStatusCode.OK); - var consumption = await response.Content.ReadFromJsonAsync(); + GetConsumption.Response? consumption = await response.Content.ReadFromJsonAsync(); consumption.Should().BeEquivalentTo(createdConsumption); } @@ -45,7 +45,7 @@ public class GetConsumptionTests : IAsyncLifetime public async Task GetConsumptions_ShouldReturnNotFound_WhenConsumptionDoesNotExist() { // Arrange - var consumptionId = Guid.NewGuid(); + Guid consumptionId = Guid.NewGuid(); // Act using HttpResponseMessage response = await _factory.HttpClient.GetAsync($"v1/consumptions{consumptionId}"); @@ -60,7 +60,7 @@ public class GetConsumptionTests : IAsyncLifetime CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id); using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest); response.EnsureSuccessStatusCode(); - var createdConsumption = await response.Content.ReadFromJsonAsync(); + CreateConsumption.Response? createdConsumption = await response.Content.ReadFromJsonAsync(); return createdConsumption!; } @@ -69,7 +69,7 @@ public class GetConsumptionTests : IAsyncLifetime CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest(); using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); - var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); + CreateCar.Response? createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); return createdCarResponse!; } diff --git a/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/UpdateConsumptionTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/UpdateConsumptionTests.cs index 9f0c1a6..09d0964 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/UpdateConsumptionTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Consumptions/UpdateConsumptionTests.cs @@ -39,7 +39,7 @@ public class UpdateConsumptionTests : IAsyncLifetime // Assert string content = await response.Content.ReadAsStringAsync(); response.StatusCode.Should().Be(HttpStatusCode.OK); - var updatedConsumption = await response.Content.ReadFromJsonAsync(); + UpdateConsumption.Response? updatedConsumption = await response.Content.ReadFromJsonAsync(); updatedConsumption.Should().BeEquivalentTo(updateConsumptionRequest, o => o.ExcludingMissingMembers()); _dbContext.Consumptions.Should().HaveCount(1) @@ -59,7 +59,7 @@ public class UpdateConsumptionTests : IAsyncLifetime // Arrange CreateConsumption.Response createdConsumption = await CreateConsumptionAsync(); UpdateConsumption.Request updateConsumptionRequest = _consumptionFaker.UpdateConsumptionRequest() with { Distance = -42 }; - var randomGuid = Guid.NewGuid(); + Guid randomGuid = Guid.NewGuid(); // Act using HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/consumptions/{randomGuid}", updateConsumptionRequest); @@ -67,7 +67,7 @@ public class UpdateConsumptionTests : IAsyncLifetime // Assert string content = await response.Content.ReadAsStringAsync(); response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - var validationProblemDetails = await response.Content.ReadFromJsonAsync(); + ValidationProblemDetails? validationProblemDetails = await response.Content.ReadFromJsonAsync(); validationProblemDetails!.Errors.Keys.Should().Contain(x => x.Equals(nameof(updateConsumptionRequest.Distance), StringComparison.OrdinalIgnoreCase)); @@ -80,7 +80,7 @@ public class UpdateConsumptionTests : IAsyncLifetime // Arrange CreateConsumption.Response createdConsumption = await CreateConsumptionAsync(); UpdateConsumption.Request updateConsumptionRequest = _consumptionFaker.UpdateConsumptionRequest(); - var randomGuid = Guid.NewGuid(); + Guid randomGuid = Guid.NewGuid(); // Act using HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/consumptions/{randomGuid}", updateConsumptionRequest); @@ -98,7 +98,7 @@ public class UpdateConsumptionTests : IAsyncLifetime CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id); using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest); response.EnsureSuccessStatusCode(); - var createdConsumption = await response.Content.ReadFromJsonAsync(); + CreateConsumption.Response? createdConsumption = await response.Content.ReadFromJsonAsync(); return createdConsumption!; } @@ -107,7 +107,7 @@ public class UpdateConsumptionTests : IAsyncLifetime CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest(); using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest); createCarResponse.EnsureSuccessStatusCode(); - var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); + CreateCar.Response? createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync(); return createdCarResponse!; } diff --git a/tests/Vegasco.Server.Api.Tests.Integration/Info/GetServerInfoTests.cs b/tests/Vegasco.Server.Api.Tests.Integration/Info/GetServerInfoTests.cs index 6c2f393..02cd58e 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/Info/GetServerInfoTests.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/Info/GetServerInfoTests.cs @@ -25,7 +25,7 @@ public class GetServerInfoTests // Assert response.IsSuccessStatusCode.Should().BeTrue(); - var serverInfo = await response.Content.ReadFromJsonAsync(); + GetServerInfo.Response? serverInfo = await response.Content.ReadFromJsonAsync(); serverInfo!.Environment.Should().NotBeEmpty(); serverInfo.CommitDate.Should().BeAfter(23.August(2024)) .And.NotBeAfter(DateTime.Now); diff --git a/tests/Vegasco.Server.Api.Tests.Integration/PostgresRespawner.cs b/tests/Vegasco.Server.Api.Tests.Integration/PostgresRespawner.cs index f852765..eed3a8d 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/PostgresRespawner.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/PostgresRespawner.cs @@ -19,7 +19,7 @@ internal sealed class PostgresRespawner : IDisposable DbConnection connection = new NpgsqlConnection(connectionString); await connection.OpenAsync(); - var respawner = await Respawner.CreateAsync(connection, + Respawner respawner = await Respawner.CreateAsync(connection, new RespawnerOptions { SchemasToInclude = ["public"], -- 2.49.1 From 5da1e2fd750634aeae856a674cb5805167cd6e26 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Tue, 24 Jun 2025 19:35:29 +0200 Subject: [PATCH 139/150] Make endpoint methods private which were not before --- src/Vegasco.Server.Api/Cars/CreateCar.cs | 2 +- src/Vegasco.Server.Api/Cars/DeleteCar.cs | 2 +- src/Vegasco.Server.Api/Cars/UpdateCar.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Vegasco.Server.Api/Cars/CreateCar.cs b/src/Vegasco.Server.Api/Cars/CreateCar.cs index 8f7ef96..aac0d7b 100644 --- a/src/Vegasco.Server.Api/Cars/CreateCar.cs +++ b/src/Vegasco.Server.Api/Cars/CreateCar.cs @@ -34,7 +34,7 @@ public static class CreateCar } } - public static async Task Endpoint( + private static async Task Endpoint( Request request, IEnumerable> validators, ApplicationDbContext dbContext, diff --git a/src/Vegasco.Server.Api/Cars/DeleteCar.cs b/src/Vegasco.Server.Api/Cars/DeleteCar.cs index 853c665..3dd1f26 100644 --- a/src/Vegasco.Server.Api/Cars/DeleteCar.cs +++ b/src/Vegasco.Server.Api/Cars/DeleteCar.cs @@ -15,7 +15,7 @@ public static class DeleteCar .Produces(404); } - public static async Task Endpoint( + private static async Task Endpoint( Guid id, ApplicationDbContext dbContext, ILoggerFactory loggerFactory, diff --git a/src/Vegasco.Server.Api/Cars/UpdateCar.cs b/src/Vegasco.Server.Api/Cars/UpdateCar.cs index ff97f20..e857eb9 100644 --- a/src/Vegasco.Server.Api/Cars/UpdateCar.cs +++ b/src/Vegasco.Server.Api/Cars/UpdateCar.cs @@ -34,7 +34,7 @@ public static class UpdateCar } } - public static async Task Endpoint( + private static async Task Endpoint( Guid id, Request request, IEnumerable> validators, -- 2.49.1 From 559804765b32be794607c409808dda392120c2fd Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Tue, 24 Jun 2025 19:43:44 +0200 Subject: [PATCH 140/150] Use persons' first names for mock data to reduce chance of conflicts Vehicle models seems to have a high enough probability that it sometimes fails --- tests/Vegasco.Server.Api.Tests.Integration/CarFaker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Vegasco.Server.Api.Tests.Integration/CarFaker.cs b/tests/Vegasco.Server.Api.Tests.Integration/CarFaker.cs index c8533d6..c562d15 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/CarFaker.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/CarFaker.cs @@ -9,11 +9,11 @@ internal class CarFaker internal CreateCar.Request CreateCarRequest() { - return new CreateCar.Request(_faker.Vehicle.Model()); + return new CreateCar.Request(_faker.Person.FirstName); } internal UpdateCar.Request UpdateCarRequest() { - return new UpdateCar.Request(_faker.Vehicle.Model()); + return new UpdateCar.Request(_faker.Person.FirstName); } } \ No newline at end of file -- 2.49.1 From 5e084ab0a8c5499d98c3641ea762d380a203b2fe Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Tue, 24 Jun 2025 20:01:45 +0200 Subject: [PATCH 141/150] Fix recurring mock data Reusing the faker instance like I used to seems to (suddenly?!) result in the same data, which newly results in a conflict response --- .../Vegasco.Server.Api.Tests.Integration/CarFaker.cs | 8 ++++---- .../ConsumptionFaker.cs | 11 +++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/tests/Vegasco.Server.Api.Tests.Integration/CarFaker.cs b/tests/Vegasco.Server.Api.Tests.Integration/CarFaker.cs index c562d15..0c21d4d 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/CarFaker.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/CarFaker.cs @@ -5,15 +5,15 @@ namespace Vegasco.Server.Api.Tests.Integration; internal class CarFaker { - private readonly Faker _faker = new(); - internal CreateCar.Request CreateCarRequest() { - return new CreateCar.Request(_faker.Person.FirstName); + Faker faker = new(); + return new CreateCar.Request(faker.Person.FirstName); } internal UpdateCar.Request UpdateCarRequest() { - return new UpdateCar.Request(_faker.Person.FirstName); + Faker faker = new(); + return new UpdateCar.Request(faker.Person.FirstName); } } \ No newline at end of file diff --git a/tests/Vegasco.Server.Api.Tests.Integration/ConsumptionFaker.cs b/tests/Vegasco.Server.Api.Tests.Integration/ConsumptionFaker.cs index 36aece2..36747c0 100644 --- a/tests/Vegasco.Server.Api.Tests.Integration/ConsumptionFaker.cs +++ b/tests/Vegasco.Server.Api.Tests.Integration/ConsumptionFaker.cs @@ -5,20 +5,19 @@ namespace Vegasco.Server.Api.Tests.Integration; internal class ConsumptionFaker { - private readonly Faker _faker = new(); - internal CreateConsumption.Request CreateConsumptionRequest(Guid carId) { + Faker faker = new(); return new CreateConsumption.Request( - _faker.Date.RecentOffset(), - _faker.Random.Int(1, 1_000), - _faker.Random.Int(20, 70), + faker.Date.RecentOffset(), + faker.Random.Int(1, 1_000), + faker.Random.Int(20, 70), carId); } internal UpdateConsumption.Request UpdateConsumptionRequest() { - CreateConsumption.Request createRequest = CreateConsumptionRequest(default); + CreateConsumption.Request createRequest = CreateConsumptionRequest(Guid.Empty); return new UpdateConsumption.Request( createRequest.DateTime, createRequest.Distance, -- 2.49.1 From 4b377ce9f4bbb5cfab4c34dc097c119cf77b59a1 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Tue, 24 Jun 2025 20:35:26 +0200 Subject: [PATCH 142/150] Fix resource name to be a valid Aspire resource name --- src/Vegasco-Web/.vscode/tasks.json | 2 +- src/Vegasco-Web/README.md | 2 +- src/Vegasco-Web/proxy.config.js | 4 ++-- src/Vegasco-Web/webserver.conf.template | 2 +- src/Vegasco.Server.AppHost.Shared/Constants.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Vegasco-Web/.vscode/tasks.json b/src/Vegasco-Web/.vscode/tasks.json index 84052b9..5fe42c3 100644 --- a/src/Vegasco-Web/.vscode/tasks.json +++ b/src/Vegasco-Web/.vscode/tasks.json @@ -14,7 +14,7 @@ "options": { "env": { "PORT": "44200", - "services__Vegasco-Server-Api__https__0": "https://localhost:7098", + "services__Api__https__0": "https://localhost:7098", "NODE_ENV": "development" } }, diff --git a/src/Vegasco-Web/README.md b/src/Vegasco-Web/README.md index 412e7db..dcb34ef 100644 --- a/src/Vegasco-Web/README.md +++ b/src/Vegasco-Web/README.md @@ -16,7 +16,7 @@ Once the server is running, open your browser and navigate to `http://localhost: Because the solution utilizes Aspire which injects endpoint references for the API as environment variables, this application uses a proxy to access the API. The proxy is configured in the `proxy.config.js` file which is used in the `serve` section of the `angular.json` file. This makes the dev server provide a proxy when serving the application. -The environment variables for the API endpoint are named `services__Vegasco-Server-Api__https__0` and `services__Vegasco-Server-Api__http__0` for the https and the http endpoints respectively. If the https endpoint is not configured, the http endpoint is used. At least one of them has to be configured. +The environment variables for the API endpoint are named `services__Api__https__0` and `services__Api__http__0` for the https and the http endpoints respectively. If the https endpoint is not configured, the http endpoint is used. At least one of them has to be configured. To allow the dev proxy to accept otherwise untrusted server certificates, set `NODE_ENV` to `development`. Otherwise the dev proxy rejects untrusted certificates. diff --git a/src/Vegasco-Web/proxy.config.js b/src/Vegasco-Web/proxy.config.js index dc3fb38..086e764 100644 --- a/src/Vegasco-Web/proxy.config.js +++ b/src/Vegasco-Web/proxy.config.js @@ -1,8 +1,8 @@ module.exports = { "/api": { target: - process.env["services__Vegasco-Server-Api__https__0"] || - process.env["services__Vegasco-Server-Api__http__0"], + process.env["services__Api__https__0"] || + process.env["services__Api__http__0"], secure: process.env["NODE_ENV"] !== "development", pathRewrite: { "^/api": "", diff --git a/src/Vegasco-Web/webserver.conf.template b/src/Vegasco-Web/webserver.conf.template index b4c7d35..095c2c1 100644 --- a/src/Vegasco-Web/webserver.conf.template +++ b/src/Vegasco-Web/webserver.conf.template @@ -2,7 +2,7 @@ server { listen 80; location ~ ^/api/(.*) { - proxy_pass ${services__Vegasco_Server_Api__https__0}/$1; + proxy_pass ${services__Api__https__0}/$1; } location / { diff --git a/src/Vegasco.Server.AppHost.Shared/Constants.cs b/src/Vegasco.Server.AppHost.Shared/Constants.cs index 9c1dc4c..4b18f9b 100644 --- a/src/Vegasco.Server.AppHost.Shared/Constants.cs +++ b/src/Vegasco.Server.AppHost.Shared/Constants.cs @@ -4,7 +4,7 @@ public static class Constants { public static class Projects { - public const string Api = "Vegasco_Server_Api"; + public const string Api = "Api"; } public static class Database -- 2.49.1 From b41d5c5d338233370359d6297429b74e495b5add Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Tue, 24 Jun 2025 20:35:49 +0200 Subject: [PATCH 143/150] Ensure correct sorting and thus also correct liter per 100km calculation --- .../src/app/modules/entries/entries/entries.component.ts | 2 +- src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts index 23d19ed..2429d27 100644 --- a/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts +++ b/src/Vegasco-Web/src/app/modules/entries/entries/entries.component.ts @@ -73,7 +73,7 @@ export class EntriesComponent implements OnInit { const entries = this.consumptionClient.getAll() .pipe( takeUntilDestroyed(), - map(response => response.consumptions), + map(response => response.consumptions.sort((a, b) => b.dateTime.localeCompare(a.dateTime))), catchError((error) => this.handleGetEntriesError(error)) ); diff --git a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs index b440142..37b1169 100644 --- a/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs +++ b/src/Vegasco.Server.Api/Consumptions/GetConsumptions.cs @@ -52,10 +52,9 @@ public static class GetConsumptions CancellationToken cancellationToken) { Dictionary> consumptionsByCar = await dbContext.Consumptions - .OrderByDescending(x => x.DateTime) .Include(x => x.Car) .GroupBy(x => x.CarId) - .ToDictionaryAsync(x => x.Key, x => x.ToList(), cancellationToken); + .ToDictionaryAsync(x => x.Key, x => x.OrderByDescending(x => x.DateTime).ToList(), cancellationToken); List responses = []; @@ -85,7 +84,10 @@ public static class GetConsumptions } } - ApiResponse apiResponse = new() { Consumptions = responses }; + ApiResponse apiResponse = new() + { + Consumptions = responses + }; return TypedResults.Ok(apiResponse); } } \ No newline at end of file -- 2.49.1 From 5062887010a20fedb4119d6a57c2d4ff3f9fb4d9 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 27 Jun 2025 19:14:50 +0200 Subject: [PATCH 144/150] Add console project to create data to migrate --- src/UploadData/Program.cs | 2000 ++++++++++++++++++++++++++++++ src/UploadData/UploadData.csproj | 14 + vegasco-server.slnx | 3 +- 3 files changed, 2016 insertions(+), 1 deletion(-) create mode 100644 src/UploadData/Program.cs create mode 100644 src/UploadData/UploadData.csproj diff --git a/src/UploadData/Program.cs b/src/UploadData/Program.cs new file mode 100644 index 0000000..88d3719 --- /dev/null +++ b/src/UploadData/Program.cs @@ -0,0 +1,2000 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json; +using System.Text.Json.Serialization; +using Vegasco.Server.Api.Cars; +using Vegasco.Server.Api.Consumptions; + +const string CARS = """ + [ + { + "id": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "name": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98" + }, + { + "id": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "name": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543" + }, + { + "id": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "name": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98" + }, + { + "id": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "name": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98" + } + ] + """; + +const string CONSUMPTION_ENTRIES = """ + [ + { + "id": "01f73963-4954-44f5-9a2d-c4bffee5b9c8", + "dateTime": "2024-04-13T14:28:21.673277Z", + "distance": 25548, + "amount": 41.77, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "0307ed64-576b-4c1d-83bf-458a2f5b5818", + "dateTime": "2023-04-24T14:57:49.570989Z", + "distance": 1471, + "amount": 43.54, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "0489d64c-e96a-4836-bfb2-d921cb7ee480", + "dateTime": "2024-03-02T22:01:34.06098Z", + "distance": 23040, + "amount": 38.96, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "0a097b4f-acd4-4821-8be3-2fe3f4aeb0ee", + "dateTime": "2023-10-06T13:57:39.833304Z", + "distance": 12749, + "amount": 38.9, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "0dcbf410-f107-4c56-8914-b21217e6bfdf", + "dateTime": "2023-07-23T17:31:31.964664Z", + "distance": 7098, + "amount": 41.31, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "119a788f-5cab-4678-998c-7cd591e3a6be", + "dateTime": "2024-01-17T19:56:19.621374Z", + "distance": 18816, + "amount": 38.52, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "13b5069b-8607-465f-84ab-5dc7cdd571c5", + "dateTime": "2024-03-19T15:36:20.770462Z", + "distance": 24146, + "amount": 34.34, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "1706ca36-8005-4ad8-8e86-9fe3c9a82b65", + "dateTime": "2023-04-12T22:12:45.342117Z", + "distance": 775, + "amount": 46.72, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "1b9e379b-c9f4-4d63-8269-04c9be526621", + "dateTime": "2023-05-17T14:48:28.58834Z", + "distance": 2654, + "amount": 39.76, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "1d8a511d-9ed5-40e3-a7d4-5b3e750e69cb", + "dateTime": "2023-06-12T13:28:50.3917Z", + "distance": 4890, + "amount": 43.25, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "1edd2675-fcf7-44f4-8290-6ebbf06c7f01", + "dateTime": "2023-05-23T11:04:35.252603Z", + "distance": 3384, + "amount": 44.1, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "27495889-5432-4ea8-9a07-2abe26e5de44", + "dateTime": "2024-04-04T20:17:05.76651Z", + "distance": 24841, + "amount": 44.58, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "302eb64c-0d03-4a7b-ba44-f92d12d1ad42", + "dateTime": "2023-08-01T15:12:40.933346Z", + "distance": 7811, + "amount": 44.17, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "37339c4b-a7ee-4035-8f26-71e14bc088ff", + "dateTime": "2023-09-07T06:28:18.969443Z", + "distance": 9838, + "amount": 39.77, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "4172e4ab-2e1f-4d5f-9c50-d0b5c91fec45", + "dateTime": "2024-03-10T16:18:50.303989Z", + "distance": 23615, + "amount": 36.26, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "469f4749-f51b-4419-a44c-9a86de744ac1", + "dateTime": "2023-08-14T20:16:43.98009Z", + "distance": 8549, + "amount": 43.66, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "52a2470e-3d51-464e-955c-f961da56fa8f", + "dateTime": "2023-11-05T09:27:06.64729Z", + "distance": 14196, + "amount": 41.89, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "571e95df-028a-4611-ad26-6f0aef9f31a2", + "dateTime": "2024-04-20T16:03:06.002924Z", + "distance": 26044, + "amount": 32.28, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "6307ae2b-987f-4943-bccc-5b2d62a5aba7", + "dateTime": "2023-06-03T14:17:12.844994Z", + "distance": 4139, + "amount": 43.35, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "65ccdc6b-8bd5-4ec1-bf4a-41353a9af7de", + "dateTime": "2024-02-10T09:02:56.546327Z", + "distance": 21230, + "amount": 42.3, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "6c1a9bbc-4903-4e23-97e7-6a1da48a910f", + "dateTime": "2023-11-29T21:13:32.703526Z", + "distance": 15536, + "amount": 41.26, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "733f6210-1091-4c0b-a2a4-5036870cc71f", + "dateTime": "2023-09-21T08:23:27.495244Z", + "distance": 11244, + "amount": 41.27, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "7e343ace-923a-4f90-af71-367fa94bedbc", + "dateTime": "2023-12-15T11:13:28.669293Z", + "distance": 16934, + "amount": 45.26, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "7f43d623-41a6-4b47-861c-27f7835e48dd", + "dateTime": "2024-01-26T17:41:23.867847Z", + "distance": 19892, + "amount": 29.1, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "8ddc6824-2593-4a09-a9d5-3657113da441", + "dateTime": "2023-07-11T10:20:32.354789Z", + "distance": 6362, + "amount": 41.45, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "92137d35-0c68-495f-83d8-4426f39d69bd", + "dateTime": "2023-09-14T14:18:46.345989Z", + "distance": 10510, + "amount": 43.46, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "a015b057-0786-419b-936e-ba20c9575d0e", + "dateTime": "2023-03-31T17:38:54.165672Z", + "distance": 30, + "amount": 40.67, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "a28d0af6-123e-4f9f-8ac3-436605f3f399", + "dateTime": "2023-05-07T16:14:14.792537Z", + "distance": 2017, + "amount": 36.52, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "a308d158-74db-4623-9724-f352792583a6", + "dateTime": "2023-09-27T12:21:51.980811Z", + "distance": 11727, + "amount": 27.12, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "a4f5f297-ba69-4473-917c-2c2d5aca6d7a", + "dateTime": "2024-01-31T15:41:40.537012Z", + "distance": 20588, + "amount": 41.92, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "c1260f03-48da-448c-9166-2a384249a42e", + "dateTime": "2023-12-25T13:00:29.83254Z", + "distance": 17529, + "amount": 37.5, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "c5edac65-cc7c-4888-af86-1a0db0ae8f87", + "dateTime": "2024-02-26T05:46:02.099197Z", + "distance": 22456, + "amount": 37.64, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "d5005e8f-1159-43ac-ad1e-67bbd7db99b3", + "dateTime": "2024-01-08T11:32:57.203414Z", + "distance": 18232, + "amount": 43.01, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "d8732260-207b-4cfa-beae-0f1123706817", + "dateTime": "2024-01-24T20:46:00.939327Z", + "distance": 19443, + "amount": 38.13, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "e25938ad-eb6a-4aac-8053-75ee37ab655b", + "dateTime": "2024-02-18T17:42:19.80403Z", + "distance": 21905, + "amount": 41.17, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "e8b7c835-8fc0-44df-8d35-ae539d612eb3", + "dateTime": "2024-04-29T14:51:04.272933Z", + "distance": 26729, + "amount": 44.2, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "eaae0fb6-1817-400d-85eb-347524138a15", + "dateTime": "2023-10-02T11:20:58.79843Z", + "distance": 11967, + "amount": 12.39, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "f5dda045-8c53-4f94-945b-5757867cb8f3", + "dateTime": "2023-08-29T13:40:37.936807Z", + "distance": 9165, + "amount": 39.92, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "f681f6e6-8e75-458b-b9ee-b837715cde62", + "dateTime": "2023-10-21T16:05:08.598667Z", + "distance": 13454, + "amount": 43.05, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "f77d532a-e53b-4449-b7c8-ea1dbad5642c", + "dateTime": "2023-06-24T18:02:57.123919Z", + "distance": 5669, + "amount": 44.84, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "fa924d18-4436-4fa9-946e-cedd227dcdf2", + "dateTime": "2023-12-08T16:07:30.413954Z", + "distance": 16241, + "amount": 42.23, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "faf2de55-5ac8-4a62-86c4-b3e4a256af97", + "dateTime": "2023-11-19T13:37:02.306525Z", + "distance": 14872, + "amount": 43.24, + "ignoreInCalculation": false, + "carId": "08db320e-b5c7-4b9f-8aab-6743b0447198", + "carName": "Focus2", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "31dcd060-3fae-479b-a9c0-ed45c0eb7d91", + "dateTime": "2024-11-26T16:48:35.310185Z", + "distance": 21948, + "amount": 40.09, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "431cb61c-09e2-400a-8ddf-130e4bea3a4a", + "dateTime": "2024-12-27T12:54:29.080303Z", + "distance": 24053, + "amount": 40.22, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "f04b414e-cdfe-4f28-81be-d05e545113d4", + "dateTime": "2024-12-13T16:19:19.809025Z", + "distance": 23350, + "amount": 38.41, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "0592edee-946e-40ab-b4ce-7c335e36e222", + "dateTime": "2022-06-11T18:33:00.849168Z", + "distance": 27214, + "amount": 37.73, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "0c0d5e92-e06c-4582-8631-18261fcaeea9", + "dateTime": "2022-09-13T11:35:36.553554Z", + "distance": 34097, + "amount": 42.7, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "0cc2a781-a2d6-4302-88f4-ef1992fb0ccc", + "dateTime": "2021-06-09T16:18:30.493941Z", + "distance": 8900, + "amount": 32.44, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "0fba66ce-9aa7-41d0-a27c-45deb6ad271b", + "dateTime": "2021-07-07T20:34:37.514765Z", + "distance": 10305, + "amount": 37.99, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "1211d9ab-7b03-4f72-97a4-3970d708bf44", + "dateTime": "2020-12-05T11:16:24.135397Z", + "distance": 2940, + "amount": 36.95, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "1f1e4251-1d21-49a3-8672-fb0b47a45a6c", + "dateTime": "2022-03-10T16:06:28.705308Z", + "distance": 22067, + "amount": 43.29, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "20108459-2786-4a3b-9850-a0f636f19fa0", + "dateTime": "2021-11-26T16:30:24.377052Z", + "distance": 17781, + "amount": 40.41, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "214f640b-917e-4f4d-b5c6-aa7e42068798", + "dateTime": "2022-05-02T17:07:51.686201Z", + "distance": 25153, + "amount": 40.47, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "220b5b1c-13c9-4224-a3cb-a4da1ba2fb83", + "dateTime": "2022-03-25T15:40:28.643524Z", + "distance": 22654, + "amount": 29.6, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "24011fde-db6b-4920-877d-fc729d86b6d0", + "dateTime": "2021-09-26T17:57:43.08389Z", + "distance": 13996, + "amount": 40.16, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "294633e4-1e4b-47de-9148-4f59197c78b5", + "dateTime": "2021-10-28T11:50:25.692511Z", + "distance": 15577, + "amount": 42.28, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "2d565629-6054-41c2-9b52-d03b9f70fa8c", + "dateTime": "2021-08-31T11:01:52.448188Z", + "distance": 12536, + "amount": 39.46, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "2e38085e-f05a-496c-8c46-a2ccc81332b4", + "dateTime": "2022-11-07T17:58:06.217348Z", + "distance": 37681, + "amount": 42.15, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "30745fd4-6f18-42b5-88c8-b934597d6476", + "dateTime": "2022-10-26T20:43:30.009959Z", + "distance": 36842, + "amount": 35.2, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "3157e5f4-26ee-45ea-b828-57be70611e98", + "dateTime": "2021-07-26T16:55:01.18988Z", + "distance": 11041, + "amount": 36.96, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "323be6e8-2d77-404d-825b-dba1d05169c3", + "dateTime": "2021-01-05T17:01:23.184485Z", + "distance": 4330, + "amount": 40.99, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "34e50168-61f9-4942-8829-86ad138c7aff", + "dateTime": "2022-08-14T14:59:24.476668Z", + "distance": 31385, + "amount": 43.97, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "36acd968-ec82-4aaf-96bc-c3c016afa20d", + "dateTime": "2022-09-03T08:24:51.333663Z", + "distance": 32582, + "amount": 15.33, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "38f77c95-a4b5-4735-b46f-1c6e4552f670", + "dateTime": "2022-02-23T23:00:00Z", + "distance": 21258, + "amount": 25.06, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "45301837-039b-4208-a54b-3121412a19d3", + "dateTime": "2021-09-08T04:49:05.032728Z", + "distance": 13211, + "amount": 35.48, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "4b9c2dd7-b77d-42cf-9b9d-4421872d4fa0", + "dateTime": "2023-03-09T14:37:10.238524Z", + "distance": 45219, + "amount": 39.23, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "4c329d8b-f2e8-4542-8c78-3c2c1f5b142e", + "dateTime": "2023-01-26T17:34:56.181247Z", + "distance": 42942, + "amount": 29.68, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "5129ec72-a354-4b21-8b3e-ac4f853dc742", + "dateTime": "2022-11-18T14:52:44.773375Z", + "distance": 38513, + "amount": 41.13, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "51b5c374-471c-4a5c-a079-1632bb0e5003", + "dateTime": "2022-07-26T08:52:12.318525Z", + "distance": 29820, + "amount": 32.48, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "55404373-8117-479e-958b-981769048095", + "dateTime": "2023-02-21T19:21:39.548129Z", + "distance": 44507, + "amount": 41.42, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "6f0c73ad-55ce-48b6-9ff3-3fd20b22a209", + "dateTime": "2022-08-30T17:42:42.867606Z", + "distance": 32275, + "amount": 45.41, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "74c75cb3-3fc3-43ff-a46c-f4f7cf26404a", + "dateTime": "2022-04-09T09:14:31.946051Z", + "distance": 23364, + "amount": 39.14, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "7ffa209f-2ff6-46eb-8c26-afe5966586c4", + "dateTime": "2022-05-19T15:30:16.958362Z", + "distance": 25970, + "amount": 43.03, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "86b2f7b6-d8cf-4c05-a295-22a681d756bf", + "dateTime": "2021-12-29T23:00:00Z", + "distance": 19245, + "amount": 42.62, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "8a53872c-063b-4d1b-9383-6661372f623b", + "dateTime": "2022-07-30T10:30:54.939251Z", + "distance": 30526, + "amount": 32.57, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "8f7ba349-d608-4900-83e7-2c1fe6c06e04", + "dateTime": "2022-07-22T18:45:51.863192Z", + "distance": 29150, + "amount": 17.99, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "9ad077f4-8bb4-4848-a10b-994a430dccaa", + "dateTime": "2022-11-29T15:41:55.846908Z", + "distance": 39230, + "amount": 37.63, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "a4827f0a-66d7-46ce-8d3a-aafeecca9030", + "dateTime": "2021-05-19T18:56:13.507733Z", + "distance": 8340, + "amount": 38.96, + "ignoreInCalculation": true, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "a681f503-14b3-4050-a052-ca74175cce21", + "dateTime": "2020-11-16T19:15:49.576109Z", + "distance": 2239, + "amount": 36.6, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "a74b7b1f-cb25-4480-90e3-8f90169394c9", + "dateTime": "2022-04-14T22:00:00Z", + "distance": 24361, + "amount": 43.88, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "a95fc036-5e77-44de-9a1e-787d6e9a275f", + "dateTime": "2023-03-23T17:59:09.404792Z", + "distance": 46060, + "amount": 43.77, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "a99be52b-22cf-4ebe-b339-79f887817199", + "dateTime": "2022-12-14T17:27:17.448214Z", + "distance": 40046, + "amount": 42.66, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "b03aa77a-58b4-4949-b230-57b1ac10606a", + "dateTime": "2022-06-24T15:19:09.718529Z", + "distance": 27972, + "amount": 40.28, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "b172172a-b48c-4ea8-8abc-727bed6ae2fe", + "dateTime": "2021-11-04T16:57:52.538748Z", + "distance": 16342, + "amount": 38.74, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "b3c0bab5-63bc-40d2-9f83-d85bb6c509c8", + "dateTime": "2022-09-07T06:09:51.093382Z", + "distance": 33236, + "amount": 33.89, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "ba354961-095d-4225-9465-3baf53b69a9c", + "dateTime": "2022-10-14T16:44:19.278493Z", + "distance": 36143, + "amount": 38.42, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "bc659bc7-5b18-4ee4-8c24-8bc5feda1fed", + "dateTime": "2023-01-12T15:28:52.295031Z", + "distance": 41489, + "amount": 42.3, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "bf8bdb29-4cc8-48df-a5b8-d8d25a796c01", + "dateTime": "2021-06-23T15:17:39.388071Z", + "distance": 9597, + "amount": 37.12, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "c1076652-48b2-4b33-935c-b083b3e755d2", + "dateTime": "2022-07-18T22:00:00Z", + "distance": 28809, + "amount": 42.89, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "c1135b21-93c2-417e-bfcb-e1cd1d236985", + "dateTime": "2022-02-05T23:00:00Z", + "distance": 20807, + "amount": 40.89, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "c1cae3bc-df27-4ec1-9e93-8610f9588863", + "dateTime": "2022-06-01T19:41:41.02851Z", + "distance": 26692, + "amount": 29.39, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "c2d8dbca-81ec-4b16-9f69-1de3f8a9a8b5", + "dateTime": "2022-01-19T17:08:19.719042Z", + "distance": 20025, + "amount": 40.05, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "ca75a9a7-6faa-4443-8b65-399f6a24b919", + "dateTime": "2021-11-17T17:20:34.226081Z", + "distance": 16990, + "amount": 32.92, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "cb980539-4296-45c7-9c40-2ee2f86bf60a", + "dateTime": "2021-10-08T05:45:13.948945Z", + "distance": 14741, + "amount": 39.52, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "d18ca527-bc73-47a5-b345-353a6509a5c0", + "dateTime": "2023-01-19T20:53:41.266591Z", + "distance": 42338, + "amount": 44.94, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "d511a7e1-275f-4e9f-81c6-00b171303c67", + "dateTime": "2020-10-20T14:52:41.897002Z", + "distance": 769, + "amount": 39.76, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "d6e1f671-fa61-4460-abb0-8650cfa83ec2", + "dateTime": "2021-01-22T19:57:12.802226Z", + "distance": 4974, + "amount": 35.19, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "d9f640d9-b0c2-4757-8bec-c881ca2d4eda", + "dateTime": "2020-12-20T11:42:07.009878Z", + "distance": 3533, + "amount": 30.63, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "db5df6e0-2726-42fa-9716-126c25c43f39", + "dateTime": "2023-02-06T21:43:29.236896Z", + "distance": 43716, + "amount": 38.89, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "f259f57d-607e-4bd9-bbc5-919320b5e077", + "dateTime": "2021-08-13T15:42:51.461757Z", + "distance": 11753, + "amount": 40.74, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "f352d571-e77e-4a06-bce7-d21e9c488f40", + "dateTime": "2022-09-22T16:26:50.697147Z", + "distance": 34895, + "amount": 42.63, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "f3ba9bd2-1a54-4857-ae1d-6e6d999aec19", + "dateTime": "2021-12-10T09:57:37.499003Z", + "distance": 18399, + "amount": 34.07, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "f9d015bf-742c-4269-8c79-445c793fd324", + "dateTime": "2022-12-25T18:40:51.9949Z", + "distance": 40656, + "amount": 34.36, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "fd0ca5d4-69a2-4145-93ef-c228fe54be57", + "dateTime": "2022-10-04T22:00:00Z", + "distance": 35660, + "amount": 28.13, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "ff00aa02-72e6-465d-a65f-1613022af079", + "dateTime": "2020-11-06T08:43:34.054224Z", + "distance": 1521, + "amount": 39.29, + "ignoreInCalculation": false, + "carId": "8800feb6-9109-4385-b52b-27c94e5dd1d8", + "carName": "Focus", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "02faf182-6930-4d98-b436-659e177f2d27", + "dateTime": "2019-10-26T22:00:00Z", + "distance": 24395, + "amount": 20.31, + "ignoreInCalculation": false, + "carId": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "carName": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "13fcf697-f6ee-4d75-b391-2f5f701265cc", + "dateTime": "2019-12-09T23:00:00Z", + "distance": 25875, + "amount": 26.6, + "ignoreInCalculation": false, + "carId": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "carName": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "2b2bd195-8ccc-4561-a755-751c3fff6080", + "dateTime": "2020-10-04T22:00:00Z", + "distance": 30558, + "amount": 15.89, + "ignoreInCalculation": false, + "carId": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "carName": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "2d9a6845-5dbf-4929-957f-33f66f4f8afc", + "dateTime": "2020-01-23T23:00:00Z", + "distance": 26881, + "amount": 24.4, + "ignoreInCalculation": false, + "carId": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "carName": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "40c83d6b-e9b5-4c3a-93c3-fac48f4a932b", + "dateTime": "2020-02-12T23:00:00Z", + "distance": 27964, + "amount": 25.29, + "ignoreInCalculation": false, + "carId": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "carName": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "43c5824e-ff0b-483a-b758-bf8c09161ea3", + "dateTime": "2020-03-18T23:00:00Z", + "distance": 28983, + "amount": 20.78, + "ignoreInCalculation": false, + "carId": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "carName": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "46fb3cd5-42f3-43f5-9009-b0f841e992d1", + "dateTime": "2020-09-11T09:28:57.231388Z", + "distance": 30035, + "amount": 22.71, + "ignoreInCalculation": false, + "carId": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "carName": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "67dfba1f-8cd5-4b29-8c4b-082c542a50fb", + "dateTime": "2020-06-12T22:00:00Z", + "distance": 29507, + "amount": 25.73, + "ignoreInCalculation": false, + "carId": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "carName": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "701e67d9-62b5-4a9e-a5e4-4cd1a75db3b2", + "dateTime": "2020-02-02T23:00:00Z", + "distance": 27397, + "amount": 24.37, + "ignoreInCalculation": false, + "carId": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "carName": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "79e2e24b-49d6-479a-88fa-662dcd616679", + "dateTime": "2019-11-25T23:00:00Z", + "distance": 25364, + "amount": 24.37, + "ignoreInCalculation": false, + "carId": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "carName": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "92915abe-42e4-4fde-a4e1-f51a94c3dc71", + "dateTime": "2020-01-09T23:00:00Z", + "distance": 26376, + "amount": 25.36, + "ignoreInCalculation": false, + "carId": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "carName": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "dcc9ae9e-f31a-4846-a16f-500e6e8b6b46", + "dateTime": "2019-11-14T23:00:00Z", + "distance": 24917, + "amount": 25.64, + "ignoreInCalculation": false, + "carId": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "carName": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "f4903978-9786-4a35-b0dd-99a5065305e7", + "dateTime": "2020-03-01T23:00:00Z", + "distance": 28498, + "amount": 26.46, + "ignoreInCalculation": false, + "carId": "fcb76d13-b02d-43c5-bc1d-2b16ad7bd2bc", + "carName": "Space Star", + "userId": "7a08af5c-31d6-479a-88a3-729e787e6c98", + "userName": "nuykent@gmail.com" + }, + { + "id": "037c47f2-187b-479e-ae8f-ec5d2ce29f54", + "dateTime": "2023-09-13T22:00:00Z", + "distance": 10914, + "amount": 42, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "054a4472-490f-4d5f-8ade-a07f6aa3b624", + "dateTime": "2022-11-06T23:00:00Z", + "distance": 7110, + "amount": 38.59, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "05590f42-d5f2-4310-8866-da19eafa8ab8", + "dateTime": "2023-04-21T22:00:00Z", + "distance": 9161, + "amount": 41.21, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "07cd9d9c-5e7d-4b1f-92ca-1d34251049d5", + "dateTime": "2024-07-19T17:26:28.557923Z", + "distance": 16281, + "amount": 35.33, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "0a590a76-8f05-4531-981a-444f2db628ea", + "dateTime": "2025-06-01T16:35:51.836058Z", + "distance": 32365, + "amount": 37.66, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "0c68b4d0-5b32-4c06-a349-3b3637f2b096", + "dateTime": "2025-02-28T21:03:03.300558Z", + "distance": 27126, + "amount": 30.85, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "13b10dae-1d2c-4197-9ac6-f6b28a6d0309", + "dateTime": "2022-06-25T22:00:00Z", + "distance": 4648, + "amount": 21.59, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "1b12c6fe-f4a2-4d89-924a-732ad0ffdc73", + "dateTime": "2024-08-21T19:53:11.630698Z", + "distance": 16904, + "amount": 36.64, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "1d731baa-caee-4ef5-aa65-622c8d83bc03", + "dateTime": "2021-04-19T22:00:00Z", + "distance": 4, + "amount": 0.1, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "1f7f8a1a-497f-4c01-9763-9cd517664f7e", + "dateTime": "2025-06-16T19:49:36.156446Z", + "distance": 33055, + "amount": 38.7, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "20638693-37ac-4b35-821a-1d5445c5cf16", + "dateTime": "2025-03-13T08:21:05.913067Z", + "distance": 27853, + "amount": 40.99, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "20820000-8aa1-4f51-bf0d-112cd156672d", + "dateTime": "2024-10-29T16:54:54.329016Z", + "distance": 19962, + "amount": 29.09, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "2235d264-6588-4648-b5b0-e49e559bbdeb", + "dateTime": "2022-04-29T22:00:00Z", + "distance": 4295, + "amount": 40.93, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "22373a99-4ca7-4c08-b833-bb49d82ffce6", + "dateTime": "2025-05-07T19:33:49.768528Z", + "distance": 31053, + "amount": 34.31, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "2c8227f2-a1f3-44d5-bc4b-2d0ebc344a2a", + "dateTime": "2021-09-15T22:00:00Z", + "distance": 1924, + "amount": 37.99, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "2fe3a2de-6c2c-41a0-8be1-ab7ed4d53607", + "dateTime": "2024-09-12T16:38:20.280842Z", + "distance": 17574, + "amount": 38.35, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "307422cf-1322-4a9a-94fb-68f4b4c7e769", + "dateTime": "2025-03-31T16:09:55.422505Z", + "distance": 29139, + "amount": 41.82, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "397e31a5-8514-4ed9-8f78-fb7be5ed783b", + "dateTime": "2022-02-08T23:00:00Z", + "distance": 3166, + "amount": 36.2, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "45aad2cf-ca12-4b12-914a-d4b2ae459f95", + "dateTime": "2025-01-10T18:55:07.933084Z", + "distance": 24677, + "amount": 36.45, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "46228ff4-550f-40e8-8223-902557ed21f7", + "dateTime": "2025-02-02T18:41:32.729285Z", + "distance": 25988, + "amount": 32.77, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "4bc9c633-98df-46cc-bc1a-59d6d42012d7", + "dateTime": "2024-10-02T19:48:47.864307Z", + "distance": 18777, + "amount": 38.26, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "4d047cf4-da5c-44c5-8bbf-964e7ccf8e85", + "dateTime": "2024-11-10T14:43:53.016738Z", + "distance": 20622, + "amount": 38.02, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "4f1afe77-2266-4125-8966-e09c9a06ea17", + "dateTime": "2025-04-26T15:54:35.911227Z", + "distance": 30462, + "amount": 37.59, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "4fccf3d2-fa34-472a-a114-e734ba99c565", + "dateTime": "2023-10-25T19:46:45.632562Z", + "distance": 11487, + "amount": 34.21, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "5028639b-cd90-4887-878c-9c6d5aa5ba83", + "dateTime": "2022-12-12T23:00:00Z", + "distance": 7766, + "amount": 38.64, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "5141c870-9070-468d-bce0-fdaf6a619009", + "dateTime": "2024-06-17T20:04:35.660765Z", + "distance": 15054, + "amount": 34.16, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "5236728a-fc44-433b-8087-06669d248434", + "dateTime": "2023-02-07T23:00:00Z", + "distance": 8475, + "amount": 42.54, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "52afd1b8-287a-42da-94c4-92a19284e1ef", + "dateTime": "2022-03-08T23:00:00Z", + "distance": 3600, + "amount": 28.47, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "52b65f25-a2e7-489e-9afa-31a71d886d80", + "dateTime": "2022-09-25T22:00:00Z", + "distance": 6437, + "amount": 40.46, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "5331e59a-e18b-41b7-b670-4ff63fbe3e54", + "dateTime": "2023-07-16T22:00:00Z", + "distance": 10194, + "amount": 61.48, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "5482802c-01e3-4cbf-bbaa-15eb1a0faeed", + "dateTime": "2021-10-21T22:00:00Z", + "distance": 2567, + "amount": 37.53, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "5a5ed2a2-6d33-4182-88f7-9387ec584063", + "dateTime": "2021-07-17T22:00:00Z", + "distance": 1280, + "amount": 34.68, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "5e57b1f6-8dfd-45cb-87be-37f55d5e10c2", + "dateTime": "2025-03-19T23:00:00Z", + "distance": 28402, + "amount": 31.6, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "79d2e147-5502-422b-b5eb-bc0537a3449b", + "dateTime": "2024-12-05T17:49:11.148738Z", + "distance": 22677, + "amount": 40.93, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "79e88e94-313e-489f-8712-41399f0e1e7a", + "dateTime": "2024-10-16T19:40:27.918829Z", + "distance": 19453, + "amount": 39.76, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "827a21dc-acdb-40d4-93b4-89134f28d582", + "dateTime": "2025-01-20T20:38:10.611457Z", + "distance": 25399, + "amount": 40.68, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "858d9bb4-dfb9-40bc-8d06-5c683b193396", + "dateTime": "2024-06-09T18:42:56.065453Z", + "distance": 14417, + "amount": 33.03, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "8fa900fc-8fc3-43e0-b30c-18c6887071dc", + "dateTime": "2023-12-03T17:46:50.161242Z", + "distance": 12161, + "amount": 39.84, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "962b55fc-4d7a-4544-9877-f9ab97b3daa7", + "dateTime": "2024-11-18T17:48:51.083936Z", + "distance": 21214, + "amount": 33.84, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "96b3b8c9-3965-4e4a-9818-9274f5391026", + "dateTime": "2022-08-28T22:00:00Z", + "distance": 5715, + "amount": 33.86, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "a7b242ed-c981-41df-b226-e280befeec63", + "dateTime": "2024-05-01T15:55:07.989666Z", + "distance": 13377, + "amount": 39.5, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "a9d767c2-b45e-4c6c-a131-8f18561068a2", + "dateTime": "2024-09-20T16:01:59.047492Z", + "distance": 18104, + "amount": 30.97, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "ae53b088-2f51-4db5-9e93-a643be9e1e53", + "dateTime": "2021-04-20T22:00:00Z", + "distance": 705, + "amount": 39.93, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "b58b114c-939e-4281-8173-e060dc4b7c6b", + "dateTime": "2025-02-16T15:59:21.91065Z", + "distance": 26606, + "amount": 36.3, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "b74ee637-bc28-45b4-b62f-f28c6c9b5be9", + "dateTime": "2025-04-09T19:47:57.154868Z", + "distance": 29798, + "amount": 37.73, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "c6f526c6-6f81-4ad1-aa3b-aa9c6f5407eb", + "dateTime": "2022-07-20T22:00:00Z", + "distance": 5145, + "amount": 28.83, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "e3f42f52-cca4-408a-844b-0319f1ab7a8b", + "dateTime": "2025-05-18T18:06:11.930089Z", + "distance": 31697, + "amount": 36.56, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "eda0795a-efe2-4df5-aea6-64554bbc03e4", + "dateTime": "2024-04-05T12:13:40.576712Z", + "distance": 12729, + "amount": 35.83, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "f3491bd7-c386-48bb-a30f-7c5b602ac0e0", + "dateTime": "2024-07-02T22:00:00Z", + "distance": 15680, + "amount": 36.43, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + }, + { + "id": "f3ebdf4f-6889-4396-ae8a-2cf1d9d36898", + "dateTime": "2024-05-08T19:43:51.888065Z", + "distance": 13831, + "amount": 26.93, + "ignoreInCalculation": false, + "carId": "08dbd004-e67a-44b5-8760-28fafe2aada6", + "carName": "Corsa", + "userId": "9c8e9ab1-bf51-408e-9a17-db3e1564a543", + "userName": "celine.hold99@gmail.com" + } + ] + """; + +const string ACCESS_TOKEN = ""; + +Car[]? cars = JsonSerializer.Deserialize(CARS); +if (cars is null) +{ + Console.WriteLine("Could not parse cars"); + Environment.ExitCode = 1; + return; +} + +ConsumptionEntry[]? consumptionEntries = JsonSerializer.Deserialize(CONSUMPTION_ENTRIES); +if (consumptionEntries is null) +{ + Console.WriteLine("Could not parse consumption entries"); + Environment.ExitCode = 2; + return; +} + + +using HttpClient httpClient = new(); +httpClient.BaseAddress = new Uri("https://api.qa.vegasco.nuyken.dev"); +httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", ACCESS_TOKEN); + +Dictionary oldCarIdToNewCarIdMapping = []; + +foreach (Car car in cars) +{ + CreateCar.Request request = new(car.Name); + using HttpResponseMessage response = await httpClient.PostAsJsonAsync("v1/cars", request); + + if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.Conflict) + { + Console.Error.WriteLine("Failed to create car '{0}', got status: {1}", car.Name, response.StatusCode); + continue; + } + + CreateCar.Response? created = await response.Content.ReadFromJsonAsync(); + if (created is null) + { + Console.Error.WriteLine( + "Could not deserialize create car response content into {0}, response status code was '{1}' and content was: {2}", + nameof(CreateCar.Response), + response.StatusCode, + await response.Content.ReadAsStringAsync()); + continue; + } + + oldCarIdToNewCarIdMapping.Add(car.Id, created.Id); +} + +foreach (ConsumptionEntry consumptionEntry in consumptionEntries) +{ + if (!oldCarIdToNewCarIdMapping.TryGetValue(consumptionEntry.CarId, out Guid newCarId)) + { + Console.Error.WriteLine("Could not determine new car id for consumption entry with old id '{0}'", consumptionEntry.Id); + continue; + } + + CreateConsumption.Request request = new( + consumptionEntry.DateTime, + consumptionEntry.Distance, + consumptionEntry.Amount, + newCarId); + + using HttpResponseMessage response = await httpClient.PostAsJsonAsync("v1/consumptions", request); + + if (!response.IsSuccessStatusCode) + { + Console.Error.WriteLine("Failed to create consumption entry with old id '{0}', got status: {1}", consumptionEntry.Id, response.StatusCode); + continue; + } + + CreateConsumption.Response? created = await response.Content.ReadFromJsonAsync(); + if (created is null) + { + Console.Error.WriteLine( + "Could not deserialize create consumption entry response content into {0}, response status code was '{1}' and content was: {2}", + nameof(CreateConsumption.Response), + response.StatusCode, + await response.Content.ReadAsStringAsync()); + continue; + } +} + +internal class Car +{ + [JsonPropertyName("id")] + public required Guid Id { get; init; } + + [JsonPropertyName("name")] + public required string Name { get; init; } +} + +internal class ConsumptionEntry +{ + [JsonPropertyName("id")] + public required Guid Id { get; init; } + + [JsonPropertyName("dateTime")] + public required DateTimeOffset DateTime { get; init; } + + [JsonPropertyName("distance")] + public required int Distance { get; init; } + + [JsonPropertyName("amount")] + public required double Amount { get; init; } + + [JsonPropertyName("carId")] + public required Guid CarId { get; init; } + + [JsonPropertyName("carName")] + public required string CarName { get; init; } +} + diff --git a/src/UploadData/UploadData.csproj b/src/UploadData/UploadData.csproj new file mode 100644 index 0000000..3becbe6 --- /dev/null +++ b/src/UploadData/UploadData.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/vegasco-server.slnx b/vegasco-server.slnx index 844625c..df254b8 100644 --- a/vegasco-server.slnx +++ b/vegasco-server.slnx @@ -6,10 +6,11 @@ + - + -- 2.49.1 From af661632cc71133824f3a362778f21bafc3c0c55 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 27 Jun 2025 19:50:27 +0200 Subject: [PATCH 145/150] Add docker dns resolver --- src/Vegasco-Web/nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Vegasco-Web/nginx.conf b/src/Vegasco-Web/nginx.conf index 0cd7817..b563b2b 100644 --- a/src/Vegasco-Web/nginx.conf +++ b/src/Vegasco-Web/nginx.conf @@ -2,7 +2,7 @@ events { } http { include mime.types; - resolver 8.8.8.8 8.8.4.4 1.1.1.1; + resolver 8.8.8.8 8.8.4.4 1.1.1.1 127.0.0.11; include /etc/nginx/conf.d/webserver.conf; } -- 2.49.1 From 9595bedd8e8c0d27a34f191ba8518991f41e50cb Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 27 Jun 2025 19:50:42 +0200 Subject: [PATCH 146/150] Add support for https and http api url environment variable --- src/Vegasco-Web/webserver.conf.template | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Vegasco-Web/webserver.conf.template b/src/Vegasco-Web/webserver.conf.template index 095c2c1..69d9a51 100644 --- a/src/Vegasco-Web/webserver.conf.template +++ b/src/Vegasco-Web/webserver.conf.template @@ -1,8 +1,16 @@ +map ${services__Api__https__0} ${DOLLAR}apiUrl { + ~.+ ${services__Api__https__0}; +} + +map ${services__Api__http__0} ${DOLLAR}apiUrl { + ~.+ ${services__Api__http__0}; +} + server { listen 80; location ~ ^/api/(.*) { - proxy_pass ${services__Api__https__0}/$1; + proxy_pass ${DOLLAR}apiUrl/${DOLLAR}1; } location / { -- 2.49.1 From 02e7ed7030ac2156123e63544afce959cd9c3d4e Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Fri, 27 Jun 2025 20:23:29 +0200 Subject: [PATCH 147/150] Fix support for https and http endpoints --- src/Vegasco-Web/Dockerfile | 2 ++ src/Vegasco-Web/webserver.conf.template | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Vegasco-Web/Dockerfile b/src/Vegasco-Web/Dockerfile index c3bdda0..2384f71 100644 --- a/src/Vegasco-Web/Dockerfile +++ b/src/Vegasco-Web/Dockerfile @@ -12,6 +12,8 @@ RUN rm /etc/nginx/conf.d/* ENV DOLLAR=$ WORKDIR /usr/share/nginx/html +ENV services__Api__https__0="" +ENV services__Api__http__0="" COPY --from=build /usr/local/app/dist/Vegasco-Web/browser . COPY nginx.conf /etc/nginx/nginx.conf COPY webserver.conf.template /etc/nginx/templates/webserver.conf.template diff --git a/src/Vegasco-Web/webserver.conf.template b/src/Vegasco-Web/webserver.conf.template index 69d9a51..65c6702 100644 --- a/src/Vegasco-Web/webserver.conf.template +++ b/src/Vegasco-Web/webserver.conf.template @@ -1,9 +1,17 @@ -map ${services__Api__https__0} ${DOLLAR}apiUrl { - ~.+ ${services__Api__https__0}; +map "write-something-here-so-it-is-not-empty" ${DOLLAR}httpsUrl { + default "${services__Api__https__0}"; } -map ${services__Api__http__0} ${DOLLAR}apiUrl { - ~.+ ${services__Api__http__0}; +map "write-something-here-so-it-is-not-empty" ${DOLLAR}httpUrl { + default "${services__Api__http__0}"; +} + +map ${DOLLAR}httpUrl ${DOLLAR}apiUrl { + ~(.+) ${DOLLAR}1; +} + +map ${DOLLAR}httpsUrl ${DOLLAR}apiUrl { + ~(.+) ${DOLLAR}1; } server { -- 2.49.1 From 8d4ae30224077be001ce2a77d4c9445370bf1245 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Jun 2025 16:54:14 +0200 Subject: [PATCH 148/150] Just go with one environment variable --- src/Vegasco-Web/webserver.conf.template | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/Vegasco-Web/webserver.conf.template b/src/Vegasco-Web/webserver.conf.template index 65c6702..480e98a 100644 --- a/src/Vegasco-Web/webserver.conf.template +++ b/src/Vegasco-Web/webserver.conf.template @@ -1,24 +1,8 @@ -map "write-something-here-so-it-is-not-empty" ${DOLLAR}httpsUrl { - default "${services__Api__https__0}"; -} - -map "write-something-here-so-it-is-not-empty" ${DOLLAR}httpUrl { - default "${services__Api__http__0}"; -} - -map ${DOLLAR}httpUrl ${DOLLAR}apiUrl { - ~(.+) ${DOLLAR}1; -} - -map ${DOLLAR}httpsUrl ${DOLLAR}apiUrl { - ~(.+) ${DOLLAR}1; -} - server { listen 80; location ~ ^/api/(.*) { - proxy_pass ${DOLLAR}apiUrl/${DOLLAR}1; + proxy_pass ${apiUrl}/${DOLLAR}1; } location / { -- 2.49.1 From ef1c1d8ba1c1af16e3626121a0330cc3f87903af Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Jun 2025 17:46:14 +0200 Subject: [PATCH 149/150] Fix resolver by always using local resolver --- src/Vegasco-Web/nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Vegasco-Web/nginx.conf b/src/Vegasco-Web/nginx.conf index b563b2b..2fc7d50 100644 --- a/src/Vegasco-Web/nginx.conf +++ b/src/Vegasco-Web/nginx.conf @@ -2,7 +2,7 @@ events { } http { include mime.types; - resolver 8.8.8.8 8.8.4.4 1.1.1.1 127.0.0.11; + resolver 127.0.0.11; include /etc/nginx/conf.d/webserver.conf; } -- 2.49.1 From 267c4165dd0c78410137ba0e90e4f5a0f4048750 Mon Sep 17 00:00:00 2001 From: ThompsonNye Date: Sat, 28 Jun 2025 17:48:16 +0200 Subject: [PATCH 150/150] Fix line endings in docker image --- src/Vegasco-Web/Dockerfile | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Vegasco-Web/Dockerfile b/src/Vegasco-Web/Dockerfile index 2384f71..7a66bc5 100644 --- a/src/Vegasco-Web/Dockerfile +++ b/src/Vegasco-Web/Dockerfile @@ -8,13 +8,12 @@ RUN pnpm "build:$CONFIGURATION" FROM nginx:alpine RUN rm /etc/nginx/conf.d/* - +RUN apk add --update dos2unix ENV DOLLAR=$ - WORKDIR /usr/share/nginx/html -ENV services__Api__https__0="" -ENV services__Api__http__0="" COPY --from=build /usr/local/app/dist/Vegasco-Web/browser . COPY nginx.conf /etc/nginx/nginx.conf +RUN dos2unix /etc/nginx/nginx.conf COPY webserver.conf.template /etc/nginx/templates/webserver.conf.template +RUN dos2unix /etc/nginx/templates/webserver.conf.template EXPOSE 80 -- 2.49.1