Merge pull request 'fix/build' (#1) from fix/build into main

Reviewed-on: #1
This commit is contained in:
Paul Schneider 2026-04-19 17:36:07 +01:00
commit e10b3df501
20 changed files with 548 additions and 287 deletions

1
.gitignore vendored
View file

@ -44,3 +44,4 @@ yavsc-pre
*.env *.env
generated/ generated/
*.lscache

View file

@ -13,5 +13,8 @@
"fr" "fr"
], ],
"cSpell.reportUnknownWords": true, "cSpell.reportUnknownWords": true,
"cSpell.language": "fr,fr-FR,en,en-GB" "cSpell.language": "fr,fr-FR,en,en-GB",
"chat.tools.terminal.autoApprove": {
"dotnet test": true
}
} }

View file

@ -5,10 +5,10 @@
<ItemGroup> <ItemGroup>
<PackageVersion Include="AsciiDocSharp" Version="0.2.0" /> <PackageVersion Include="AsciiDocSharp" Version="0.2.0" />
<PackageVersion Include="AsciiDocSharp.Converters.Html" Version="0.2.0" /> <PackageVersion Include="AsciiDocSharp.Converters.Html" Version="0.2.0" />
<PackageVersion Include="bootstrap" Version="5.3.7" /> <PackageVersion Include="bootstrap" Version="5.3.8" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" /> <PackageVersion Include="coverlet.collector" Version="10.0.0" />
<PackageVersion Include="Google.Apis.Calendar.v3" Version="1.69.0.3746" /> <PackageVersion Include="Google.Apis.Calendar.v3" Version="1.73.0.4073" />
<PackageVersion Include="Google.Apis.Compute.v1" Version="1.70.0.3871" /> <PackageVersion Include="Google.Apis.Compute.v1" Version="1.73.0.4107" />
<PackageVersion Include="HigginsSoft.IdentityServer8" Version="8.0.5-preview-net9" /> <PackageVersion Include="HigginsSoft.IdentityServer8" Version="8.0.5-preview-net9" />
<PackageVersion Include="HigginsSoft.IdentityServer8.AspNetIdentity" Version="8.0.5-preview-net9" /> <PackageVersion Include="HigginsSoft.IdentityServer8.AspNetIdentity" Version="8.0.5-preview-net9" />
<PackageVersion Include="HigginsSoft.IdentityServer8.EntityFramework" Version="8.0.5-preview-net9" /> <PackageVersion Include="HigginsSoft.IdentityServer8.EntityFramework" Version="8.0.5-preview-net9" />
@ -17,46 +17,47 @@
<PackageVersion Include="HigginsSoft.IdentityServer8.Storage" Version="8.0.5-preview-net9" /> <PackageVersion Include="HigginsSoft.IdentityServer8.Storage" Version="8.0.5-preview-net9" />
<PackageVersion Include="IdentityModel.AspNetCore" Version="4.3.0" /> <PackageVersion Include="IdentityModel.AspNetCore" Version="4.3.0" />
<PackageVersion Include="IdentityModel.OidcClient" Version="6.0.0" /> <PackageVersion Include="IdentityModel.OidcClient" Version="6.0.0" />
<PackageVersion Include="MailKit" Version="4.8.0" /> <PackageVersion Include="MailKit" Version="4.16.0" />
<PackageVersion Include="Microsoft.AspNetCore.Antiforgery" Version="2.3.0" /> <PackageVersion Include="Microsoft.AspNetCore.Antiforgery" Version="2.3.9" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Google" Version="9.0.7" /> <PackageVersion Include="Microsoft.AspNetCore.Authentication.Google" Version="9.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.7" /> <PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="9.0.7" /> <PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="9.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Hosting" Version="2.3.0" /> <PackageVersion Include="Microsoft.AspNetCore.Hosting" Version="2.3.9" />
<PackageVersion Include="Microsoft.AspNetCore.Http.Features" Version="5.0.17" /> <PackageVersion Include="Microsoft.AspNetCore.Http.Features" Version="5.0.17" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.7" /> <PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="9.0.7" /> <PackageVersion Include="Microsoft.AspNetCore.Identity.UI" Version="10.0.6" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.7" /> <PackageVersion Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Razor" Version="2.3.0" /> <PackageVersion Include="Microsoft.AspNetCore.Razor" Version="2.3.0" />
<PackageVersion Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" /> <PackageVersion Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.36" />
<PackageVersion Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" /> <PackageVersion Include="Microsoft.AspNetCore.SignalR" Version="1.2.9" />
<PackageVersion Include="Microsoft.AspNetCore.StaticFiles" Version="2.3.0" /> <PackageVersion Include="Microsoft.AspNetCore.StaticFiles" Version="2.3.9" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.7" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.7" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.6" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.7" /> <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.6" />
<PackageVersion Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" /> <PackageVersion Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="9.0.7" /> <PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.6" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.7" /> <PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.6" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.7" /> <PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.6" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.7" /> <PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.6" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.7" /> <PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.6" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.4.0" />
<PackageVersion Include="Microsoft.Playwright" Version="1.53.0" /> <PackageVersion Include="Microsoft.Playwright" Version="1.59.0" />
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" /> <PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageVersion Include="MimeKit" Version="4.8.0" /> <PackageVersion Include="MimeKit" Version="4.16.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" /> <PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" /> <PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageVersion Include="PayPalMerchantSDK" Version="2.16.250" /> <PackageVersion Include="PayPalMerchantSDK" Version="2.16.250" />
<PackageVersion Include="pazof.rules" Version="1.1.3" /> <PackageVersion Include="pazof.rules" Version="1.1.3" />
<PackageVersion Include="popper.js" Version="1.16.1" /> <PackageVersion Include="popper.js" Version="1.16.1" />
<PackageVersion Include="RazorEngine.NetCore" Version="3.1.0" /> <PackageVersion Include="RazorEngine.NetCore" Version="3.1.1" />
<PackageVersion Include="Selenium.WebDriver" Version="4.34.0" /> <PackageVersion Include="Selenium.WebDriver" Version="4.43.0" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" /> <PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.10" /> <PackageVersion Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="9.0.3" /> <PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.7" />
<PackageVersion Include="System.Security.Cryptography.Pkcs" Version="9.0.7" /> <PackageVersion Include="System.Security.Cryptography.Pkcs" Version="10.0.6" />
<PackageVersion Include="xunit" Version="2.9.3" /> <PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.2" /> <PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>1c73094f-959f-4211-b1a1-6a69b236c283</UserSecretsId> <UserSecretsId>1c73094f-959f-4211-b1a1-6a69b236c283</UserSecretsId>
<RootNamespace>Yavsc.Api</RootNamespace> <RootNamespace>Yavsc.Api</RootNamespace>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>1c73094f-959f-4211-b1a1-6a69b236c283</UserSecretsId> <UserSecretsId>1c73094f-959f-4211-b1a1-6a69b236c283</UserSecretsId>
<RootNamespace>Yavsc.Api</RootNamespace> <RootNamespace>Yavsc.Api</RootNamespace>

View file

@ -1,16 +1,18 @@
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IdentityModel.Tokens.Jwt;
using System.Reflection;
using Google.Apis.Util.Store; using Google.Apis.Util.Store;
using IdentityModel;
using IdentityServer8; using IdentityServer8;
using Microsoft.Extensions.DependencyInjection; using IdentityServer8.EntityFramework.Entities;
using IdentityServer8.EntityFramework.Services;
using IdentityServer8.EntityFramework.Stores;
using IdentityServer8.Stores; using IdentityServer8.Stores;
using IdentityServer8.EntityFramework; using IdentityServer8.Validation;
using IdentityServer8.Extensions;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor;
@ -22,28 +24,12 @@ using Microsoft.Net.Http.Headers;
using Newtonsoft.Json; using Newtonsoft.Json;
using Yavsc.Helpers; using Yavsc.Helpers;
using Yavsc.Interface; using Yavsc.Interface;
using Yavsc.Interfaces;
using Yavsc.Models; using Yavsc.Models;
using Yavsc.Server.Helpers;
using Yavsc.Services; using Yavsc.Services;
using Yavsc.Settings; using Yavsc.Settings;
using Yavsc.ViewModels.Auth; using Yavsc.ViewModels.Auth;
using Yavsc.Server.Helpers;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Protocols.Configuration;
using IdentityModel;
using Yavsc.Interfaces;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Npgsql;
using System.Reflection;
using IdentityServer8.EntityFramework.DbContexts;
using IdentityServer8.EntityFramework.Mappers;
using System.IdentityModel.Tokens.Jwt;
using IdentityServer8.EntityFramework.Stores;
using IdentityServer8.EntityFramework.Services;
using IdentityServer8.EntityFramework.Interfaces;
using Microsoft.AspNetCore.Authentication.Cookies;
using IdentityServer8.Validation;
using IdentityServer8.EntityFramework.Entities;
namespace Yavsc.Extensions; namespace Yavsc.Extensions;
@ -99,9 +85,20 @@ public static class HostingExtensions
options.ResourcesPath = "Resources"; options.ResourcesPath = "Resources";
}).AddDataAnnotationsLocalization(); }).AddDataAnnotationsLocalization();
bool useTestEmailSender = builder.Configuration.GetValue<bool>("UseTestEmailSender", false);
if (useTestEmailSender)
{
services.AddTransient<ITrueEmailSender, TestMailSender>()
.AddTransient<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender, TestMailSender>();
}
else
{
services.AddTransient<ITrueEmailSender, MailSender>() services.AddTransient<ITrueEmailSender, MailSender>()
.AddTransient<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender, MailSender>() .AddTransient<Microsoft.AspNetCore.Identity.UI.Services.IEmailSender, MailSender>();
.AddTransient<IYavscMessageSender, YavscMessageSender>() }
services.AddTransient<IYavscMessageSender, YavscMessageSender>()
.AddTransient<IBillingService, BillingService>() .AddTransient<IBillingService, BillingService>()
.AddTransient<IDataStore, FileDataStore>((sp) => new FileDataStore("googledatastore", false)) .AddTransient<IDataStore, FileDataStore>((sp) => new FileDataStore("googledatastore", false))
.AddTransient<ICalendarManager, CalendarManager>() .AddTransient<ICalendarManager, CalendarManager>()
@ -150,13 +147,23 @@ public static class HostingExtensions
public static IdentityBuilder AddIdentityDBAndStores(this WebApplicationBuilder builder) public static IdentityBuilder AddIdentityDBAndStores(this WebApplicationBuilder builder)
{ {
IServiceCollection services = builder.Services; IServiceCollection services = builder.Services;
bool useInMemory = builder.Configuration.GetValue<bool>("UseInMemoryDatabase", false);
if (useInMemory)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase("YavscInMemory"));
}
else
{
services.AddDbContext<ApplicationDbContext>(options => services.AddDbContext<ApplicationDbContext>(options =>
{ {
options.UseNpgsql(builder.Configuration.GetConnectionString(Constants.YavscConnectionStringName), options.UseNpgsql(builder.Configuration.GetConnectionString(Constants.YavscConnectionStringName),
options => options.MigrationsAssembly(typeof(Program).Assembly)); options => options.MigrationsAssembly(typeof(Program).Assembly));
}); });
}
return services.AddIdentity<ApplicationUser, IdentityRole>( var identityBuilder = services.AddIdentity<ApplicationUser, IdentityRole>(
options => options =>
{ {
options.SignIn.RequireConfirmedAccount = builder.Environment.IsEnvironment( options.SignIn.RequireConfirmedAccount = builder.Environment.IsEnvironment(
@ -166,6 +173,10 @@ public static class HostingExtensions
} }
) )
.AddEntityFrameworkStores<ApplicationDbContext>(); .AddEntityFrameworkStores<ApplicationDbContext>();
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>();
return identityBuilder;
} }
private static void AddYavscPolicies(IServiceCollection services) private static void AddYavscPolicies(IServiceCollection services)
@ -259,7 +270,7 @@ public static class HostingExtensions
}); });
} }
private static IIdentityServerBuilder AddIdentityServer(WebApplicationBuilder builder) public static IIdentityServerBuilder AddIdentityServer(WebApplicationBuilder builder)
{ {
builder.Services.Configure<IdentityOptions>(options => builder.Services.Configure<IdentityOptions>(options =>
{ {
@ -269,6 +280,8 @@ public static class HostingExtensions
}); });
var migrationsAssembly = typeof(Program).GetTypeInfo().Assembly.GetName().Name; var migrationsAssembly = typeof(Program).GetTypeInfo().Assembly.GetName().Name;
var connectionString = builder.Configuration.GetConnectionString(Constants.YavscConnectionStringName); var connectionString = builder.Configuration.GetConnectionString(Constants.YavscConnectionStringName);
bool useInMemory = builder.Configuration.GetValue<bool>("UseInMemoryDatabase", false);
string inMemoryDatabaseName = "YavscInMemory";
var identityServerBuilder = builder.Services.AddIdentityServer(options => var identityServerBuilder = builder.Services.AddIdentityServer(options =>
{ {
@ -287,6 +300,12 @@ public static class HostingExtensions
.AddCorsPolicyService<CorsPolicyService>() .AddCorsPolicyService<CorsPolicyService>()
.AddResourceStore<ResourceStore>() .AddResourceStore<ResourceStore>()
.AddConfigurationStore(options => .AddConfigurationStore(options =>
{
if (useInMemory)
{
options.ConfigureDbContext = b => b.UseInMemoryDatabase(inMemoryDatabaseName);
}
else
{ {
options.ConfigureDbContext = b => b.UseNpgsql(connectionString, options.ConfigureDbContext = b => b.UseNpgsql(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly)) sql => sql.MigrationsAssembly(migrationsAssembly))
@ -303,11 +322,19 @@ public static class HostingExtensions
} }
}); });
}
}) })
.AddOperationalStore(options => .AddOperationalStore(options =>
{
if (useInMemory)
{
options.ConfigureDbContext = b => b.UseInMemoryDatabase(inMemoryDatabaseName);
}
else
{ {
options.ConfigureDbContext = b => b.UseNpgsql(connectionString, options.ConfigureDbContext = b => b.UseNpgsql(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly)); sql => sql.MigrationsAssembly(migrationsAssembly));
}
}); });
if (builder.Environment.IsDevelopment()) if (builder.Environment.IsDevelopment())

View file

@ -50,7 +50,7 @@ namespace Yavsc.Helpers
} }
public static string GetSender(this ApplicationUser user) public static string GetSender(this ApplicationUser user)
{ {
return user.UserName+" ["+user.Id+"@"+Config.Authority+"]"; return user.UserName+" ["+user.Id+"@"+Config.AuthorityDomain+"]";
} }
public static HairCutQueryEvent CreateEvent(this HairMultiCutQuery query, public static HairCutQueryEvent CreateEvent(this HairMultiCutQuery query,
IStringLocalizer SR, BrusherProfile bpr) IStringLocalizer SR, BrusherProfile bpr)

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<PackageLicenseExpression>WTFPL</PackageLicenseExpression> <PackageLicenseExpression>WTFPL</PackageLicenseExpression>
<UserSecretsId>76e56fc2-1619-40d8-8393-365258b7a21d</UserSecretsId> <UserSecretsId>76e56fc2-1619-40d8-8393-365258b7a21d</UserSecretsId>
@ -31,6 +31,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" />
<PackageReference Include="Google.Apis.Compute.v1" /> <PackageReference Include="Google.Apis.Compute.v1" />

View file

@ -9,8 +9,24 @@ namespace Yavsc;
public static class Config public static class Config
{ {
/// <summary>
/// Authority URL for IdentityServer, used for authentication and authorization.
/// </summary>
public static string Authority { get; set; } public static string Authority { get; set; }
public static string AuthorityDomain
{
get
{
if (Uri.TryCreate(Authority, UriKind.Absolute, out var uri))
{
return uri.GetLeftPart(UriPartial.Authority);
}
throw new InvalidOperationException("Invalid Authority URL");
}
}
public static IConfigurationRoot? GoogleWebClientConfiguration { get; set; } public static IConfigurationRoot? GoogleWebClientConfiguration { get; set; }
public static GoogleServiceAccount? GServiceAccount { get; set; } public static GoogleServiceAccount? GServiceAccount { get; set; }

View file

@ -1,5 +1,4 @@
namespace Yavsc.Helpers namespace Yavsc.Helpers
{ {
using System.Collections.Generic; using System.Collections.Generic;
@ -16,6 +15,9 @@ namespace Yavsc.Helpers
public static class WorkflowHelpers public static class WorkflowHelpers
{ {
// Synchronization lock for billing service configuration
private static readonly object _billingLock = new object();
public static async Task<List<PerformerProfileViewModel>> public static async Task<List<PerformerProfileViewModel>>
ListPerformersAsync(this ApplicationDbContext context, ListPerformersAsync(this ApplicationDbContext context,
IBillingService billing, IBillingService billing,
@ -41,17 +43,41 @@ namespace Yavsc.Helpers
public static void RegisterBilling<T>(string code, Func<ApplicationDbContext, long, public static void RegisterBilling<T>(string code, Func<ApplicationDbContext, long,
IDecidableQuery> getter) where T : IBillable IDecidableQuery> getter) where T : IBillable
{ {
if (BillingService.Billing.ContainsKey(code) lock (_billingLock)
|| BillingService.GlobalBillingMap.ContainsKey(code)) {
string typeName = typeof(T).Name;
// Only add if not already present (idempotent operation)
if (!BillingService.Billing.ContainsKey(code))
{ {
throw new InvalidOperationException("Billing setup");
}
BillingService.Billing.Add(code, getter); BillingService.Billing.Add(code, getter);
BillingService.GlobalBillingMap.Add(typeof(T).Name, code); }
else if (!BillingService.GlobalBillingMap.ContainsKey(typeName) ||
BillingService.GlobalBillingMap[typeName] != code)
{
throw new InvalidOperationException($"Billing setup: code '{code}' already registered");
}
if (!BillingService.GlobalBillingMap.ContainsKey(typeName))
{
BillingService.GlobalBillingMap.Add(typeName, code);
}
else if (BillingService.GlobalBillingMap[typeName] != code)
{
throw new InvalidOperationException($"Billing setup: type '{typeName}' already registered with different code");
}
}
} }
public static void ConfigureBillingService() public static void ConfigureBillingService()
{ {
lock (_billingLock)
{
BillingService.Billing.Clear();
BillingService.GlobalBillingMap.Clear();
BillingService.UserSettings.Clear();
Config.ProfileTypes.Clear();
foreach (var a in System.AppDomain.CurrentDomain.GetAssemblies()) foreach (var a in System.AppDomain.CurrentDomain.GetAssemblies())
{ {
foreach (var c in a.GetTypes()) foreach (var c in a.GetTypes())
@ -90,6 +116,7 @@ namespace Yavsc.Helpers
RegisterBilling<RdvQuery>(BillingCodes.Rdv, new Func<ApplicationDbContext, long, IDecidableQuery> RegisterBilling<RdvQuery>(BillingCodes.Rdv, new Func<ApplicationDbContext, long, IDecidableQuery>
((db, id) => db.RdvQueries.Include(q => q.Regularisation).Single(q => q.Id == id))); ((db, id) => db.RdvQueries.Include(q => q.Regularisation).Single(q => q.Id == id)));
} }
}
} }
} }

View file

@ -0,0 +1,30 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.Extensions.Logging;
using Yavsc.Interface;
namespace Yavsc.Services
{
public class TestMailSender : ITrueEmailSender, IEmailSender
{
private readonly ILogger<TestMailSender> logger;
public TestMailSender(ILoggerFactory loggerFactory)
{
logger = loggerFactory.CreateLogger<TestMailSender>();
}
public Task SendEmailAsync(string email, string subject, string htmlMessage)
{
logger.LogInformation("[TestMailSender] SendEmailAsync to {Email} subject={Subject}", email, subject);
return Task.CompletedTask;
}
public Task<string> SendEmailAsync(string name, string email, string subject, string htmlMessage)
{
logger.LogInformation("[TestMailSender] SendEmailAsync to {Email} subject={Subject} name={Name}", email, subject, name);
return Task.FromResult($"test-message-{Guid.NewGuid()}");
}
}
}

View file

@ -3,6 +3,12 @@ namespace Yavsc.Settings
public class SmtpSettings public class SmtpSettings
{ {
public string Server { get; set; } public string Server { get; set; }
public string Host
{
get => Server;
set => Server = value;
}
public int Port { get; set; } public int Port { get; set; }
public string SenderName { get; set; } public string SenderName { get; set; }

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>53bd70e8-ff81-497a-847f-a15fd8ea7a09</UserSecretsId> <UserSecretsId>53bd70e8-ff81-497a-847f-a15fd8ea7a09</UserSecretsId>
<RootNamespace>Yavsc.Server</RootNamespace> <RootNamespace>Yavsc.Server</RootNamespace>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>Yavsc.cli</RootNamespace> <RootNamespace>Yavsc.cli</RootNamespace>
</PropertyGroup> </PropertyGroup>

View file

@ -1,9 +1,8 @@
using isnd.tests; using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
using isnd.tests;
using Xunit.Abstractions; using Xunit.Abstractions;
using IdentityModel.Client; using IdentityModel.Client;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Net.Security;
namespace yavscTests namespace yavscTests
{ {
@ -21,13 +20,12 @@ namespace yavscTests
[Fact] [Fact]
public async Task ObtainServiceToken() public async Task ObtainServiceToken()
{ {
var serverUrl = _serverFixture.Addresses.FirstOrDefault( var serverUrl = _serverFixture.Addresses.FirstOrDefault(u => u.StartsWith("https:"));
u => u.StartsWith("https:") if (string.IsNullOrEmpty(serverUrl))
); throw new InvalidOperationException("No HTTPS server address found");
String authority = _serverFixture.SiteSettings.Authority;
HttpClient client = NewHttpClient(); HttpClient client = NewHttpClient();
var disco = await client.GetDiscoveryDocumentAsync(authority); var disco = await client.GetDiscoveryDocumentAsync(serverUrl);
if (disco.IsError) throw new Exception(disco.Error); if (disco.IsError) throw new Exception(disco.Error);
var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest var response = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
@ -38,10 +36,7 @@ namespace yavscTests
Scope = "test", Scope = "test",
GrantType = "client_credentials" GrantType = "client_credentials"
}); });
/*"mvc";
options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0";*/
if (response.IsError) throw new Exception(response.Error); if (response.IsError) throw new Exception(response.Error);
} }
private static HttpClient NewHttpClient() private static HttpClient NewHttpClient()
@ -52,27 +47,22 @@ namespace yavscTests
[Fact] [Fact]
public async Task ObtainResourceOwnerPasswordToken() public async Task ObtainResourceOwnerPasswordToken()
{ {
var serverUrl = _serverFixture.Addresses.FirstOrDefault( var serverUrl = _serverFixture.Addresses.FirstOrDefault(u => u.StartsWith("https:"));
u => u.StartsWith("https:") if (string.IsNullOrEmpty(serverUrl))
); throw new InvalidOperationException("No HTTPS server address found");
String authority = _serverFixture.SiteSettings.Authority;
var client = NewHttpClient(); var client = NewHttpClient();
var disco = await client.GetDiscoveryDocumentAsync(authority); var disco = await client.GetDiscoveryDocumentAsync(serverUrl);
if (disco.IsError) throw new Exception(disco.Error); if (disco.IsError) throw new Exception(disco.Error);
var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{ {
Address = disco.TokenEndpoint, Address = disco.TokenEndpoint,
ClientId = _serverFixture.TestClientId, ClientId = _serverFixture.TestClientId,
ClientSecret = _serverFixture.TestClientSecret, ClientSecret = _serverFixture.TestClientSecret,
UserName = _serverFixture.TestingUserName, UserName = _serverFixture.TestingUserName,
Password = _serverFixture.TestingUserPassword, Password = _serverFixture.TestingUserPassword,
Scope = "test", Scope = "test",
Parameters = Parameters =
{ {
{ "acr_values", "tenant:custom_account_store1 foo bar quux" } { "acr_values", "tenant:custom_account_store1 foo bar quux" }
@ -100,8 +90,8 @@ namespace yavscTests
private bool ValidateCertificate( private bool ValidateCertificate(
HttpRequestMessage request, HttpRequestMessage request,
X509Certificate2 certificate, X509Certificate2? certificate,
X509Chain chain, X509Chain? chain,
SslPolicyErrors errors) SslPolicyErrors errors)
{ {
// Accept all certificates (bypass validation) // Accept all certificates (bypass validation)

View file

@ -0,0 +1,48 @@
using Microsoft.EntityFrameworkCore;
using Xunit;
using Yavsc;
using Yavsc.Abstract.Workflow;
using Yavsc.Helpers;
using Yavsc.Models;
using Yavsc.Models.Billing;
using Yavsc.Models.Haircut;
using Yavsc.Services;
namespace yavscTests
{
[Trait("regression", "II")]
public class BillingServiceTests
{
[Fact]
public void ConfigureBillingService_CanBeCalledTwiceWithoutThrowing()
{
// First initialization should populate the billing registry.
WorkflowHelpers.ConfigureBillingService();
int firstBillingCount = BillingService.Billing.Count;
int firstSettingsCount = BillingService.UserSettings.Count;
int firstProfileTypesCount = Config.ProfileTypes.Count;
// Second call should be idempotent and not throw.
WorkflowHelpers.ConfigureBillingService();
Assert.Equal(firstBillingCount, BillingService.Billing.Count);
Assert.Equal(firstSettingsCount, BillingService.UserSettings.Count);
Assert.Equal(firstProfileTypesCount, Config.ProfileTypes.Count);
}
[Fact]
public void RegisterBilling_DuplicateRegistrationThrowsInvalidOperationException()
{
WorkflowHelpers.ConfigureBillingService();
var firstRegistrar = new Func<ApplicationDbContext, long, IDecidableQuery>((db, id) =>
db.HairCutQueries.Include(q => q.Prestation).Include(q => q.Regularisation).Single(q => q.Id == id));
const string testCode = "TestBrush";
Assert.Throws<InvalidOperationException>(() =>
WorkflowHelpers.RegisterBilling<HairCutQuery>(testCode, firstRegistrar));
}
}
}

View file

@ -1,20 +1,25 @@
using IdentityServer8.EntityFramework.Entities;
using IdentityServer8.Models;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Yavsc;
using Yavsc.Models;
using Yavsc.Extensions;
using Microsoft.EntityFrameworkCore;
using Serilog; using Serilog;
using Serilog.Events; using Serilog.Events;
using Serilog.Sinks.SystemConsole.Themes; using Serilog.Sinks.SystemConsole.Themes;
using IdentityServer8.EntityFramework.Entities; using System.Net;
using IdentityServer8.Models; using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Yavsc;
using Yavsc.Extensions;
using Yavsc.Models;
using Client = IdentityServer8.EntityFramework.Entities.Client; using Client = IdentityServer8.EntityFramework.Entities.Client;
namespace isnd.tests namespace isnd.tests
@ -23,35 +28,92 @@ namespace isnd.tests
[CollectionDefinition("Web server collection")] [CollectionDefinition("Web server collection")]
public class WebServerFixture : IDisposable public class WebServerFixture : IDisposable
{ {
private static readonly Lazy<X509Certificate2> _selfSignedCertificate = new Lazy<X509Certificate2>(CreateSelfSignedCertificate);
private static readonly object _sync = new object();
private static WebApplication? _app;
private static bool _isInitialized = false;
private static int _instanceCount = 0;
private static readonly List<string> _sharedAddresses = new List<string>();
private static string? _sharedTestClientId;
private static string? _sharedTestClientSecret;
private static string? _sharedTestingUserName;
private static string? _sharedTestingUserPassword;
private static string? _sharedTestingUserEmail;
private static IServiceProvider? _sharedServices;
private static IConfiguration? _sharedConfiguration;
private static SiteSettings? _sharedSiteSettings;
private static Microsoft.Extensions.Logging.ILogger? _sharedLogger;
public List<string> Addresses { get; private set; } = new List<string>(); public List<string> Addresses { get; private set; } = new List<string>();
public Microsoft.Extensions.Logging.ILogger Logger { get; internal set; } public Microsoft.Extensions.Logging.ILogger? Logger { get; internal set; }
private SiteSettings siteSettings; private SiteSettings? siteSettings;
public IConfigurationRoot Configuration { get; private set; } public IConfiguration? Configuration { get; private set; }
private WebApplication app; public string? TestClientId { get; private set; }
public string TestClientId { get; private set; }
public IServiceProvider Services { get; private set; } public IServiceProvider? Services { get; private set; }
public string TestingUserName { get; private set; } public string? TestingUserName { get; private set; }
public string TestingUserPassword { get; private set; } public string? TestingUserPassword { get; private set; }
public string ProtectedTestingApiKey { get; internal set; } public string? ProtectedTestingApiKey { get; internal set; }
public ApplicationUser TestingUser { get; private set; } public ApplicationUser? TestingUser { get; private set; }
public bool DbCreated { get; internal set; } public bool DbCreated { get; internal set; }
public SiteSettings SiteSettings { get => siteSettings; set => siteSettings = value; } public SiteSettings? SiteSettings { get => siteSettings; set => siteSettings = value; }
public string TestClientSecret { get; set; } public string? TestClientSecret { get; set; }
public string? TestingUserEmail { get; set; }
public WebServerFixture() public WebServerFixture()
{
lock (_sync)
{
_instanceCount++;
if (!_isInitialized)
{ {
SetupHost().Wait(); SetupHost().Wait();
_isInitialized = true;
}
CopySharedState();
}
} }
public void Dispose() public void Dispose()
{ {
if (app != null) lock (_sync)
app.StopAsync().Wait(); {
_instanceCount--;
if (_instanceCount == 0 && _app != null)
{
_app.StopAsync().Wait();
_app = null;
_isInitialized = false;
_sharedAddresses.Clear();
_sharedServices = null;
_sharedConfiguration = null;
_sharedSiteSettings = null;
_sharedLogger = null;
_sharedTestClientId = null;
_sharedTestClientSecret = null;
_sharedTestingUserName = null;
_sharedTestingUserPassword = null;
_sharedTestingUserEmail = null;
}
}
}
private void CopySharedState()
{
Addresses = new List<string>(_sharedAddresses);
Logger = _sharedLogger;
Configuration = _sharedConfiguration;
Services = _sharedServices;
SiteSettings = _sharedSiteSettings;
TestClientId = _sharedTestClientId;
TestClientSecret = _sharedTestClientSecret;
TestingUserName = _sharedTestingUserName;
TestingUserPassword = _sharedTestingUserPassword;
TestingUserEmail = _sharedTestingUserEmail;
} }
void ConfigureLogger() => Log.Logger = new LoggerConfiguration() void ConfigureLogger() => Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug() .MinimumLevel.Debug()
@ -73,18 +135,38 @@ namespace isnd.tests
{ {
var builder = WebApplication.CreateBuilder(); var builder = WebApplication.CreateBuilder();
builder.Environment.EnvironmentName = "Development";
ConfigureLogger(); ConfigureLogger();
Configuration = builder.Configuration builder.Configuration
.AddJsonFile("appsettings.json") .AddJsonFile("appsettings.json", optional: false, reloadOnChange: false)
.AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true) .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: false)
.AddEnvironmentVariables() .AddEnvironmentVariables()
.Build(); .AddInMemoryCollection(new Dictionary<string, string?>
{
["UseInMemoryDatabase"] = "true",
["UseTestEmailSender"] = "true",
["Smtp:Host"] = "localhost",
["Smtp:Port"] = "25",
["Smtp:SenderName"] = "Yavsc Test",
["Smtp:SenderEmail"] = "test@example.com"
});
this.app = builder.ConfigureWebAppServices(); // Configure Kestrel for HTTPS with self-signed certificate on a dynamic port
Services = app.Services; builder.WebHost.ConfigureKestrel(options =>
SiteSettings = app.Services.GetRequiredService<IOptions<SiteSettings>>().Value; {
options.Listen(IPAddress.Loopback, 0, listenOptions =>
{
listenOptions.UseHttps(_selfSignedCertificate.Value);
});
});
using (var migrationScope = app.Services.CreateScope()) Configuration = builder.Configuration;
_app = builder.ConfigureWebAppServices();
Services = _app.Services;
SiteSettings = _app.Services.GetRequiredService<IOptions<SiteSettings>>().Value;
using (var migrationScope = _app.Services.CreateScope())
{ {
var db = migrationScope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var db = migrationScope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
db.Database.EnsureDeleted(); db.Database.EnsureDeleted();
@ -92,42 +174,52 @@ namespace isnd.tests
TestingUserName = "Tester"; TestingUserName = "Tester";
TestingUserPassword = "tesT456+*"; TestingUserPassword = "tesT456+*";
TestClientId = "testClientId"; TestClientId = "testClientId";
TestingUserEmail = "test@no-reply.com";
TestingUser = null;
TestClientSecret = Guid.CreateVersion7().ToString(); TestClientSecret = Guid.CreateVersion7().ToString();
EnsureUser(TestingUserName, TestingUserPassword); EnsureUser(TestingUserName, TestingUserPassword, TestingUserEmail, migrationScope);
AddAuthorizedClient(TestClientId, TestClientSecret); AddAuthorizedClient(migrationScope, TestClientId, TestClientSecret);
TestingUser = await db.Users.FirstOrDefaultAsync(u => u.UserName == TestingUserName); TestingUser = await db.Users.FirstOrDefaultAsync(u => u.UserName == TestingUserName);
} }
await app.ConfigurePipeline(); await _app!.ConfigurePipeline();
app.UseSession(); _app.UseSession();
await app.StartAsync(); await _app.StartAsync();
_sharedServices = _app.Services;
_sharedConfiguration = Configuration;
_sharedSiteSettings = SiteSettings;
_sharedTestClientId = TestClientId;
_sharedTestClientSecret = TestClientSecret;
_sharedTestingUserName = TestingUserName;
_sharedTestingUserPassword = TestingUserPassword;
_sharedTestingUserEmail = TestingUserEmail;
_sharedLogger = _app.Services.GetRequiredService<ILoggerFactory>().CreateLogger<WebServerFixture>();
Logger = _sharedLogger;
var server = _app.Services.GetRequiredService<IServer>();
var logFactory = app.Services.GetRequiredService<ILoggerFactory>();
Logger = logFactory.CreateLogger<WebServerFixture>();
var server = app.Services.GetRequiredService<IServer>();
var addressFeatures = server.Features.Get<IServerAddressesFeature>(); var addressFeatures = server.Features.Get<IServerAddressesFeature>();
if (addressFeatures?.Addresses != null)
{
_sharedAddresses.Clear();
foreach (var address in addressFeatures.Addresses) foreach (var address in addressFeatures.Addresses)
{ {
_sharedAddresses.Add(address);
Addresses.Add(address); Addresses.Add(address);
} }
}
} }
private void AddAuthorizedClient(string testClientId, string testClientSecret) private void AddAuthorizedClient(IServiceScope scope, string testClientId, string testClientSecret)
{
using (IServiceScope scope = app.Services.CreateScope())
{ {
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
Client testingClient = new Client Client testingClient = new Client
{ {
ClientId = testClientId, ClientId = testClientId,
AccessTokenLifetime = 3600000, AccessTokenLifetime = 3600000,
AccessTokenType = 1, AccessTokenType = 1,
BackChannelLogoutUri = SiteSettings.Audience, BackChannelLogoutUri = SiteSettings!.Audience,
ClientName = "Testing client", ClientName = "Testing client",
Enabled = true Enabled = true
}; };
@ -143,7 +235,7 @@ namespace isnd.tests
var testOrigin = new ClientCorsOrigin var testOrigin = new ClientCorsOrigin
{ {
ClientId = testingClient.Id, ClientId = testingClient.Id,
Origin = SiteSettings.Audience Origin = SiteSettings!.Audience
}; };
db.ClientCorsOrigins.Add(testOrigin); db.ClientCorsOrigins.Add(testOrigin);
@ -175,19 +267,17 @@ namespace isnd.tests
db.ClientRedirectUris.Add(new ClientRedirectUri db.ClientRedirectUris.Add(new ClientRedirectUri
{ {
ClientId = testingClient.Id, ClientId = testingClient.Id,
RedirectUri = SiteSettings.Audience RedirectUri = SiteSettings!.Audience
}); });
db.SaveChanges(); db.SaveChanges();
} }
}
public void EnsureUser(string testingUserName, string password) public void EnsureUser(string testingUserName, string password, string email, IServiceScope scope)
{ {
if (TestingUser == null) if (TestingUser == null)
{ {
using IServiceScope scope = app.Services.CreateScope();
var userManager = var userManager =
scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>(); scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
@ -208,5 +298,21 @@ namespace isnd.tests
TestingUser = dbContext.Users.FirstOrDefault(u => u.UserName == testingUserName); TestingUser = dbContext.Users.FirstOrDefault(u => u.UserName == testingUserName);
} }
} }
private static X509Certificate2 CreateSelfSignedCertificate()
{
var rsa = RSA.Create(2048);
var certRequest = new CertificateRequest("CN=localhost", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
certRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
certRequest.CertificateExtensions.Add(
new X509EnhancedKeyUsageExtension(
new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));
var certificate = certRequest.CreateSelfSigned(new DateTimeOffset(DateTime.UtcNow.AddDays(-1)), new DateTimeOffset(DateTime.UtcNow.AddDays(3650)));
return certificate;
}
} }
} }

View file

@ -1,6 +1,6 @@
{ {
"Site": { "Site": {
"Authority": "localhost", "Authority": "https://localhost",
"Title": "Yavsc dev", "Title": "Yavsc dev",
"Slogan": "Yavsc : WIP.", "Slogan": "Yavsc : WIP.",
"Banner": "/images/yavsc.png", "Banner": "/images/yavsc.png",
@ -41,7 +41,7 @@
} }
}, },
"ConnectionStrings": { "ConnectionStrings": {
"YavscConnection": "Server=lame-NpgsqlHostName;Port=5432;Database=lame-DataBase;Username=lame-Username;Password=lame-dbPassword;", "YavscConnection": "Server=lame-NpgsqlHostName;Port=5432;Database=lame-DataBase;Username=lame-Username;Password=lame-dbPassword;"
}, },
"DataProtection": { "DataProtection": {
"Keys": { "Keys": {

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
@ -24,6 +24,11 @@
<PackageReference Include="xunit" /> <PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" /> <PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Yavsc.Org\Yavsc.Org.csproj" /> <ProjectReference Include="..\..\src\Yavsc.Org\Yavsc.Org.csproj" />
<ProjectReference Include="..\..\src\Yavsc.Abstract\Yavsc.Abstract.csproj" /> <ProjectReference Include="..\..\src\Yavsc.Abstract\Yavsc.Abstract.csproj" />