diff --git a/.vscode/launch.json b/.vscode/launch.json
index dc8d3c68d..d5088f01d 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -4,6 +4,22 @@
// Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
+ {
+ "name": "PostIt Desktop",
+ "type": "dotnet",
+ "request": "launch",
+ "projectPath": "${workspaceFolder}/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj"
+ },
+ {
+ "name": "PostIt Desktop local",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "${workspaceFolder}/src/PostIt/PostIt.Desktop/bin/Debug/net10.0/PostIt.Desktop.dll",
+ "env": {
+ "POSTIT_SETTINGS_JSON": "/home/paul/Workspace/yavsc/src/PostIt/PostIt/postit-settings.json"
+ },
+ "preLaunchTask": "dotnet: build-postit-desktop"
+ },
{
"name": "Android Debug",
"type": "mono",
@@ -20,10 +36,10 @@
"port": 55555
},
{
- "name": "API",
+ "name": "Yavsc API",
"type": "dotnet",
"request": "launch",
- "projectPath": "${workspaceFolder}/src/Api/Api.csproj"
+ "projectPath": "${workspaceFolder}/src/Yavsc.Api/Yavsc.Api.csproj"
},
{
"name": "Yavsc Org",
@@ -37,12 +53,7 @@
"request": "launch",
"projectPath": "${workspaceFolder}/src/Yavsc.Blogs/Yavsc.Blogs.csproj"
},
- {
- "name": "PostIt Desktop",
- "type": "dotnet",
- "request": "launch",
- "projectPath": "${workspaceFolder}/src/PostIt/PostIt.Desktop/PostIt.Desktop.csproj",
- },
+
{
"name": "Test PostIt.Android launch (Xamarin.UITest)",
"type": "coreclr",
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index f5bcc21a2..71384cd8c 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -69,7 +69,7 @@
{
"label": "test api backend (npgsql)",
"type": "process",
- "problemMatcher": ["$msCompile"],
+ "problemMatcher": "$msCompile",
"command": "dotnet",
"args": [
"test",
@@ -80,7 +80,7 @@
"options": {
"cwd": "src/Yavsc.Api.Test",
"env": {
- "YAVSC_API_TEST_DB_PROVIDER": "npgsql",
+ "YAVSC_API_TEST_DB_PROVIDER": "npgsql"
}
},
"group": {
@@ -101,6 +101,21 @@
"kind": "build"
},
"isBackground": true
+ },
+ {
+ "label": "dotnet: build-postit-desktop",
+ "type": "process",
+ "isBuildCommand": true,
+ "isTestCommand": false,
+ "isBackground": true,
+ "command": "dotnet",
+ "args": ["build", "/property:GenerateFullPaths=true"],
+ "options": {
+ "cwd": "src/PostIt/PostIt.Desktop"
+ },
+ "group": {
+ "kind": "build"
+ }
}
]
}
diff --git a/contrib/README.md b/contrib/README.md
deleted file mode 100644
index 011561560..000000000
--- a/contrib/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Read me
-
-## Note aux icones
-
-ใยฎ๐
ฌโโ๐ฉ๐ฉบ๐ซ๐ฎ๐๐๐๐ด๐ต๐ฒ๐๐ง๐ฉ๐๐๐พ๐ผ๐ฌ๐ญ๐ฟ๐พ๐ท๐ฏ๐น๐๐๐ฉ
diff --git a/src/PostIt/PostIt/ViewModels/Settings/AuthenticationSettings.cs b/src/PostIt/PostIt/ViewModels/Settings/AuthenticationSettings.cs
index 8034820d9..3c23187e4 100644
--- a/src/PostIt/PostIt/ViewModels/Settings/AuthenticationSettings.cs
+++ b/src/PostIt/PostIt/ViewModels/Settings/AuthenticationSettings.cs
@@ -22,7 +22,7 @@ public partial class AuthenticationSettings : ObservableObject
public const string DefaultClientId = "postit";
- public static readonly string[] DefaultScopes = { "blogs" };
+ public static readonly string[] DefaultScopes = { "blogs", "api" };
[ObservableProperty]
public partial string Authority { get; set; }
diff --git a/src/PostIt/PostIt/ViewModels/Settings/Settings.cs b/src/PostIt/PostIt/ViewModels/Settings/Settings.cs
index 6b6ed5d4f..35d5a59fe 100644
--- a/src/PostIt/PostIt/ViewModels/Settings/Settings.cs
+++ b/src/PostIt/PostIt/ViewModels/Settings/Settings.cs
@@ -15,7 +15,8 @@ namespace PostIt.ViewModels;
public partial class Settings : ViewModelBase
{
- public string SettingsFileName {get; private set;} = "postit-settings.json";
+ [JsonIgnore]
+ public string? SettingsFileFullName { get; private set; }
[ObservableProperty]
public partial AuthenticationSettings Authentication { get; set; } = new();
@@ -39,6 +40,60 @@ public partial class Settings : ViewModelBase
[JsonIgnore]
public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret.");
+
+ public bool Loaded { get; private set; } = false;
+
+
+ ///
+ /// True when the in-memory state has drifted from the last
+ /// or snapshot. The
+ /// Settings page binds the Sauver button's IsEnabled to
+ /// this flag, so it only enables when the user has actually
+ /// touched something since the last load / save. Cleared by
+ /// (and by ), set by
+ /// every successful setter on the four top-level mutable
+ /// properties and on the sub-properties of
+ /// .
+ ///
+ [ObservableProperty]
+ public partial bool IsDirty { get; private set; } = false;
+
+
+ ///
+ /// Guards every mutation of the observable state. [ObservableProperty]
+ /// generates setters that call SetProperty(...) which fires
+ /// PropertyChanged. Avalonia bindings consume that event on
+ /// the UI thread, and a stray background-thread update is exactly
+ /// what crashed DataValidationErrors.SetErrors on
+ /// postit://callback re-launches. The lock makes mutations
+ /// atomic;
+ /// then marshals the notification onto the UI thread so bindings
+ /// observe the change on the right thread.
+ ///
+ private readonly object _mutationGate = new();
+
+ ///
+ /// Scopes the PostIt client always requires from the OIDC provider,
+ /// regardless of what the user has in their settings file.
+ ///
+ /// PostIt calls into the Blog API (and any other Yavsc API
+ /// gated by an [Authorize("โฆScope")] policy) and is silent
+ /// about the contract: a missing scope here surfaces as a 401
+ /// on the very first API call after login, with no obvious link
+ /// to the settings. The "feature" scopes the user must opt into
+ /// (e.g. blogs) are still their choice โ we only force the
+ /// structural ones that OIDC itself needs.
+ ///
+ private static readonly string[] BuiltInScopes = new[]
+ {
+ "openid", // OIDC: required for the id_token
+ "profile", // OIDC: standard profile claims
+ "offline_access", // OIDC: required to receive a refresh_token
+ "blogs",
+ "api"
+ };
+ private readonly string DEFAULT_SETTINGS_FILENAME = "postit-settings.json";
+
public void SetActionStatus(string message, StatusSeverity severity = StatusSeverity.Info)
{
ActionStatus = severity switch
@@ -86,35 +141,6 @@ public partial class Settings : ViewModelBase
MarkDirty();
}
- public bool Loaded { get; private set; } = false;
-
- ///
- /// True when the in-memory state has drifted from the last
- /// or snapshot. The
- /// Settings page binds the Sauver button's IsEnabled to
- /// this flag, so it only enables when the user has actually
- /// touched something since the last load / save. Cleared by
- /// (and by ), set by
- /// every successful setter on the four top-level mutable
- /// properties and on the sub-properties of
- /// .
- ///
- [ObservableProperty]
- public partial bool IsDirty { get; private set; } = false;
-
- ///
- /// Guards every mutation of the observable state. [ObservableProperty]
- /// generates setters that call SetProperty(...) which fires
- /// PropertyChanged. Avalonia bindings consume that event on
- /// the UI thread, and a stray background-thread update is exactly
- /// what crashed DataValidationErrors.SetErrors on
- /// postit://callback re-launches. The lock makes mutations
- /// atomic;
- /// then marshals the notification onto the UI thread so bindings
- /// observe the change on the right thread.
- ///
- private readonly object _mutationGate = new();
-
///
/// Build OidcClient options configured for Authorization Code + PKCE
/// (no client secret). The browser implementation should be supplied
@@ -178,27 +204,6 @@ public partial class Settings : ViewModelBase
Authentication.RefreshScopeListText();
}
- ///
- /// Scopes the PostIt client always requires from the OIDC provider,
- /// regardless of what the user has in their settings file.
- ///
- /// PostIt calls into the Blog API (and any other Yavsc API
- /// gated by an [Authorize("โฆScope")] policy) and is silent
- /// about the contract: a missing scope here surfaces as a 401
- /// on the very first API call after login, with no obvious link
- /// to the settings. The "feature" scopes the user must opt into
- /// (e.g. blogs) are still their choice โ we only force the
- /// structural ones that OIDC itself needs.
- ///
- private static readonly string[] BuiltInScopes = new[]
- {
- "openid", // OIDC: required for the id_token
- "profile", // OIDC: standard profile claims
- "offline_access", // OIDC: required to receive a refresh_token
- "blogs",
- "api"
-
- };
///
/// Merge user-configured scopes with the built-in ones. User scopes
@@ -255,7 +260,14 @@ public partial class Settings : ViewModelBase
&& !string.IsNullOrWhiteSpace(envJson))
{
Console.WriteLine("๐ Loading settings from POSTIT_SETTINGS_JSON environment variable.");
- ApplyJson(envJson, "POSTIT_SETTINGS_JSON");
+ FileInfo configByEnvFileInfo = new FileInfo(envJson);
+ if (!configByEnvFileInfo.Exists)
+ {
+ throw new Exception($"๐ฉ Settings file not found at {configByEnvFileInfo.FullName}");
+ }
+ string json = File.ReadAllText(configByEnvFileInfo.FullName);
+ ApplyJson(json, "POSTIT_SETTINGS_JSON");
+ SettingsFileFullName = configByEnvFileInfo.FullName;
Loaded = true;
return;
}
@@ -264,9 +276,22 @@ public partial class Settings : ViewModelBase
"PostIt"
);
- string configPath = Path.Combine(configDir, SettingsFileName);
+ if (SettingsFileFullName is not null)
+ {
+ // Already set by a previous Load() or by the environment
+ // variable path above. Use it as-is.
+ }
+ else if (Environment.GetEnvironmentVariable("POSTIT_SETTINGS_JSON") is string envPath
+ && !string.IsNullOrWhiteSpace(envPath))
+ {
+ SettingsFileFullName = envPath;
+ }
+ else
+ {
+ SettingsFileFullName = Path.Combine(configDir, "postit-settings.json");
+ }
- FileInfo configFileInfo = new FileInfo(configPath);
+ FileInfo configFileInfo = new FileInfo(SettingsFileFullName);
if (!configFileInfo.Exists)
{
@@ -295,6 +320,7 @@ public partial class Settings : ViewModelBase
using var reader = new StreamReader(stream);
var json = reader.ReadToEnd();
ApplyJson(json, $"user file {configFileInfo.FullName}");
+ SettingsFileFullName = configFileInfo.FullName;
Loaded = true;
}
catch (Exception ex)
@@ -458,11 +484,17 @@ public partial class Settings : ViewModelBase
{
SetActionStatus("Enregistrement des parametres...", StatusSeverity.Info);
- var configDir = Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
- "PostIt");
- Directory.CreateDirectory(configDir);
- var configPath = Path.Combine(configDir, SettingsFileName);
+ if (SettingsFileFullName is null)
+ {
+ var configDir = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "PostIt");
+ Directory.CreateDirectory(configDir);
+ SettingsFileFullName = Path.Combine(configDir, DEFAULT_SETTINGS_FILENAME);
+ }
+
+ var configPath = SettingsFileFullName!;
+ Directory.CreateDirectory(Path.GetDirectoryName(configPath)!);
lock (_mutationGate)
{
diff --git a/src/PostIt/PostIt/postit-settings sample.json b/src/PostIt/PostIt/postit-settings sample.json
deleted file mode 100644
index e23839b1f..000000000
--- a/src/PostIt/PostIt/postit-settings sample.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "Authentication": {
- "ClientId": "postit",
- "Authority": "https://yavsc.pschneider.fr"
- },
- "RedirectUri": "postit://callback",
- "DarkMode": false,
- "ApiUrl": "https://api.pschneider.fr/api/v1/",
- "Scopes": [
- "openid",
- "profile",
- "offline_access",
- "blogs"
- ]
-}
diff --git a/src/PostIt/PostIt/postit-settings-sample.json b/src/PostIt/PostIt/postit-settings-sample.json
new file mode 100644
index 000000000..fbf1f68a0
--- /dev/null
+++ b/src/PostIt/PostIt/postit-settings-sample.json
@@ -0,0 +1,17 @@
+{
+ "Authentication": {
+ "ClientId": "postit",
+ "Authority": "https://yavsc.pschneider.fr",
+ "Scopes": [
+ "openid",
+ "profile",
+ "offline_access",
+ "blogs",
+ "api"
+ ],
+ "RedirectUri": "postit://callback"
+ },
+ "DarkMode": false,
+ "ApiUrl": "https://api.pschneider.fr/api/v1/"
+
+}
diff --git a/src/PostIt/PostIt/postit-settings.json b/src/PostIt/PostIt/postit-settings.json
index 080fed178..f62a9ac1c 100644
--- a/src/PostIt/PostIt/postit-settings.json
+++ b/src/PostIt/PostIt/postit-settings.json
@@ -1,15 +1,21 @@
{
- "Authentication": {
- "ClientId": "postit",
- "Authority": "https://yavsc.pschneider.fr/"
- },
- "RedirectUri": "postit://callback",
- "DarkMode": true,
- "ApiUrl": "https://api.pschneider.fr/api/v1/",
+ "Authentication": {
+ "Authority": "https://localhost:5001",
+ "ClientId": "postit",
"Scopes": [
- "openid",
- "profile",
- "offline_access",
- "blogs"
- ]
-}
+ "blogs",
+ "api"
+ ],
+ "RedirectUri": "postit://callback"
+ },
+ "DarkMode": true,
+ "BlogsApiUrl": "https://localhost:5003/api/v1/",
+ "ApiUrl": "https://localhost:5005/api/v1/",
+ "SearchText": "",
+ "ProviderOngoingRequestsSortOption": "",
+ "Loaded": true,
+ "IsDirty": true,
+ "CanNavigateNext": false,
+ "CanNavigatePrevious": true,
+ "SaveCommand": {}
+}
\ No newline at end of file
diff --git a/src/Yavsc.Api.Test/BillingControllerTests.cs b/src/Yavsc.Api.Test/BillingControllerTests.cs
index 43c2df729..38b9a9919 100644
--- a/src/Yavsc.Api.Test/BillingControllerTests.cs
+++ b/src/Yavsc.Api.Test/BillingControllerTests.cs
@@ -114,7 +114,7 @@ public sealed class BillingControllerTests : IClassFixture
var db = scope.ServiceProvider.GetRequiredService();
db.Database.ExecuteSqlInterpolated($@"
-INSERT INTO ""NominativeServiceCommand""
+INSERT INTO ""NominativeServiceCommands""
(""ActivityCode"", ""ClientId"", ""Consent"", ""DateCreated"", ""DateModified"", ""Description"", ""Discriminator"", ""PerformerId"", ""Status"", ""UserCreated"", ""UserModified"")
VALUES
({"dev"}, {"bob"}, {true}, {DateTime.UtcNow.AddMinutes(-5)}, {DateTime.UtcNow.AddMinutes(-4)}, {"Legacy malformed row"}, {""}, {"alice"}, {(int)QueryStatus.Accepted}, {"alice"}, {"alice"});
diff --git a/src/Yavsc.Api.Test/EstimateApiControllerTests.cs b/src/Yavsc.Api.Test/EstimateApiControllerTests.cs
index 10a9c000b..f48f81ce5 100644
--- a/src/Yavsc.Api.Test/EstimateApiControllerTests.cs
+++ b/src/Yavsc.Api.Test/EstimateApiControllerTests.cs
@@ -101,7 +101,7 @@ public sealed class EstimateApiControllerTests : IClassFixture(opt =>
- opt.UseNpgsql(npgsqlConnectionString));
+ opt.UseNpgsql(npgsqlConnectionString,
+ x => x.MigrationsAssembly("Yavsc.Org")));
}
else
{
@@ -99,7 +101,18 @@ public sealed class ApiWebServerFixture : WebHostFixture
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService();
- db.Database.EnsureCreated();
+ if (UseNpgsqlProvider())
+ {
+ // Apply the EF Core migrations (Yavsc.Org assembly) so the
+ // test database schema matches the production provider.
+ // EnsureCreated must not be used here: it would create the
+ // schema without the migrations history and break Migrate().
+ db.Database.Migrate();
+ }
+ else
+ {
+ db.Database.EnsureCreated();
+ }
}
await Task.CompletedTask;
@@ -303,17 +316,23 @@ public sealed class ApiWebServerFixture : WebHostFixture
{
if (UseNpgsqlProvider())
{
- db.Set().RemoveRange(db.Set());
- db.Set().RemoveRange(db.Set());
- db.Set().RemoveRange(db.Set());
- db.Set().RemoveRange(db.Set());
- db.Set().RemoveRange(db.Set());
- db.Set().RemoveRange(db.Set());
- db.Set().RemoveRange(db.Set());
- db.Set().RemoveRange(db.Set());
- db.Set().RemoveRange(db.Set());
- db.Set().RemoveRange(db.Set());
-
+ // Purge only the tables of the test graph, children before
+ // parents, so no DELETE violates a foreign key. Estimate and
+ // CommandLine reference NominativeServiceCommand (CommandId) and
+ // must be deleted before the Rdv/HairCut/HairMultiCut queries.
+ db.Set().RemoveRange(db.Set());
+ db.Set().RemoveRange(db.Set());
+ db.Set().RemoveRange(db.Set());
+ db.Set().RemoveRange(db.Set());
+ db.Set().RemoveRange(db.Set());
+ db.Set().RemoveRange(db.Set());
+ db.Set().RemoveRange(db.Set());
+ db.Set().RemoveRange(db.Set());
+ db.Set().RemoveRange(db.Set());
+ db.Set().RemoveRange(db.Set());
+ db.Set().RemoveRange(db.Set());
+ db.Set().RemoveRange(db.Set());
+
db.SaveChanges();
return;
}
@@ -321,72 +340,6 @@ public sealed class ApiWebServerFixture : WebHostFixture
db.Database.EnsureDeleted();
}
-
-
- private static IReadOnlyList GetDeletionOrder(IModel model)
- {
- var entityTypes = model
- .GetEntityTypes()
- .Where(et =>
- et.ClrType is not null &&
- !et.IsOwned() &&
- et.FindPrimaryKey() is not null)
- .ToArray();
-
- var included = new HashSet(entityTypes);
- var dependencies = new Dictionary>();
-
- foreach (var entityType in entityTypes)
- {
- var principals = entityType
- .GetForeignKeys()
- .Where(fk => !fk.IsOwnership)
- .Select(fk => fk.PrincipalEntityType)
- .Where(included.Contains)
- .ToHashSet();
-
- dependencies[entityType] = principals;
- }
-
- var queue = new Queue(
- dependencies.Where(kvp => kvp.Value.Count == 0).Select(kvp => kvp.Key));
-
- var order = new List(entityTypes.Length);
-
- while (queue.Count > 0)
- {
- var current = queue.Dequeue();
- if (!order.Contains(current))
- {
- order.Add(current);
- }
-
- foreach (var kvp in dependencies)
- {
- if (!kvp.Value.Remove(current) || kvp.Value.Count != 0)
- {
- continue;
- }
-
- if (!order.Contains(kvp.Key) && !queue.Contains(kvp.Key))
- {
- queue.Enqueue(kvp.Key);
- }
- }
- }
-
- // If cycles remain (rare), append unresolved types last and rely on DB cascades.
- foreach (var entityType in entityTypes)
- {
- if (!order.Contains(entityType))
- {
- order.Add(entityType);
- }
- }
-
- return order;
- }
-
public void ResetAndSeedRdvQueryGraph()
{
ResetAndSeedActivityGraph();
diff --git a/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs b/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs
index 94a9b14dd..3e3e870c1 100644
--- a/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs
+++ b/src/Yavsc.Api/Controllers/Business/EstimateApiController.cs
@@ -125,7 +125,7 @@ namespace Yavsc.Controllers
if (estimate.CommandId != null)
{
- var query = _context.Set()
+ var query = _context.NominativeServiceCommands
.FirstOrDefault(q => q.Id == estimate.CommandId);
if (query == null)
{
diff --git a/src/Yavsc.Api/Properties/launchSettings.json b/src/Yavsc.Api/Properties/launchSettings.json
deleted file mode 100644
index 3ede67745..000000000
--- a/src/Yavsc.Api/Properties/launchSettings.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "$schema": "http://json.schemastore.org/launchsettings.json",
- "iisSettings": {
- "windowsAuthentication": false,
- "anonymousAuthentication": true,
- "iisExpress": {
- "applicationUrl": "https://localhost:6001",
- "sslPort": 6001
- }
- },
- "profiles": {
- "https": {
- "commandName": "Project",
- "dotnetRunMessages": true,
- "launchBrowser": false,
- "applicationUrl": "https://localhost:6001",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- },
- "IIS Express": {
- "commandName": "IISExpress",
- "launchBrowser": true,
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- }
- }
-}
diff --git a/src/Yavsc.Api/appsettings-api.json b/src/Yavsc.Api/appsettings-api.json
index d8b295de0..6783f565e 100644
--- a/src/Yavsc.Api/appsettings-api.json
+++ b/src/Yavsc.Api/appsettings-api.json
@@ -1,6 +1,7 @@
{
"Site": {
"Authority": "https://localhost:5001",
+ "Audience": ["api"],
"CorsAllowedOrigins": [
"https://localhost:5003",
"https://yavsc.pschneider.fr"
diff --git a/src/Yavsc.Org/Migrations/20260913191414_NominativeServiceCommand.Designer.cs b/src/Yavsc.Org/Migrations/20260913191414_NominativeServiceCommand.Designer.cs
new file mode 100644
index 000000000..be80669af
--- /dev/null
+++ b/src/Yavsc.Org/Migrations/20260913191414_NominativeServiceCommand.Designer.cs
@@ -0,0 +1,4714 @@
+๏ปฟ//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+using Yavsc.Models;
+
+#nullable disable
+
+namespace Yavsc.Migrations
+{
+ [DbContext(typeof(ApplicationDbContext))]
+ [Migration("20260913191414_NominativeServiceCommand")]
+ partial class NominativeServiceCommand
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "10.0.9")
+ .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+ NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResource", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
+
+ b.Property("AllowedAccessTokenSigningAlgorithms")
+ .HasColumnType("text");
+
+ b.Property("Created")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("DisplayName")
+ .HasColumnType("text");
+
+ b.Property("Enabled")
+ .HasColumnType("boolean");
+
+ b.Property("LastAccessed")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("NonEditable")
+ .HasColumnType("boolean");
+
+ b.Property("ShowInDiscoveryDocument")
+ .HasColumnType("boolean");
+
+ b.Property("Updated")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.ToTable("ApiResources");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ApiResourceId")
+ .HasColumnType("integer");
+
+ b.Property("ApiResourceId1")
+ .HasColumnType("integer");
+
+ b.Property("Type")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApiResourceId");
+
+ b.HasIndex("ApiResourceId1");
+
+ b.ToTable("ApiResourceClaims");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceProperty", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ApiResourceId")
+ .HasColumnType("integer");
+
+ b.Property("ApiResourceId1")
+ .HasColumnType("integer");
+
+ b.Property("Key")
+ .HasColumnType("text");
+
+ b.Property("Value")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApiResourceId");
+
+ b.HasIndex("ApiResourceId1");
+
+ b.ToTable("ApiResourceProperties");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceScope", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ApiResourceId")
+ .HasColumnType("integer");
+
+ b.Property("ApiResourceId1")
+ .HasColumnType("integer");
+
+ b.Property("Scope")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApiResourceId");
+
+ b.HasIndex("ApiResourceId1");
+
+ b.ToTable("ApiResourceScopes");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiResourceSecret", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ApiResourceId")
+ .HasColumnType("integer");
+
+ b.Property("ApiResourceId1")
+ .HasColumnType("integer");
+
+ b.Property("Created")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Expiration")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Type")
+ .HasColumnType("text");
+
+ b.Property("Value")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ApiResourceId");
+
+ b.HasIndex("ApiResourceId1");
+
+ b.ToTable("ApiResourceSecrets");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScope", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("DisplayName")
+ .HasColumnType("text");
+
+ b.Property("Emphasize")
+ .HasColumnType("boolean");
+
+ b.Property("Enabled")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("Required")
+ .HasColumnType("boolean");
+
+ b.Property("ShowInDiscoveryDocument")
+ .HasColumnType("boolean");
+
+ b.HasKey("Id");
+
+ b.ToTable("ApiScopes");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ScopeId")
+ .HasColumnType("integer");
+
+ b.Property("ScopeId1")
+ .HasColumnType("integer");
+
+ b.Property("Type")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ScopeId");
+
+ b.HasIndex("ScopeId1");
+
+ b.ToTable("ApiScopeClaims");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ApiScopeProperty", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Key")
+ .HasColumnType("text");
+
+ b.Property("ScopeId")
+ .HasColumnType("integer");
+
+ b.Property("ScopeId1")
+ .HasColumnType("integer");
+
+ b.Property("Value")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ScopeId");
+
+ b.HasIndex("ScopeId1");
+
+ b.ToTable("ApiScopeProperties");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.Client", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
+
+ b.Property("AbsoluteRefreshTokenLifetime")
+ .HasColumnType("integer");
+
+ b.Property("AccessTokenLifetime")
+ .HasColumnType("integer");
+
+ b.Property("AccessTokenType")
+ .HasColumnType("integer");
+
+ b.Property("AllowAccessTokensViaBrowser")
+ .HasColumnType("boolean");
+
+ b.Property("AllowOfflineAccess")
+ .HasColumnType("boolean");
+
+ b.Property("AllowPlainTextPkce")
+ .HasColumnType("boolean");
+
+ b.Property("AllowRememberConsent")
+ .HasColumnType("boolean");
+
+ b.Property("AllowedIdentityTokenSigningAlgorithms")
+ .HasColumnType("text");
+
+ b.Property("AlwaysIncludeUserClaimsInIdToken")
+ .HasColumnType("boolean");
+
+ b.Property("AlwaysSendClientClaims")
+ .HasColumnType("boolean");
+
+ b.Property("AuthorizationCodeLifetime")
+ .HasColumnType("integer");
+
+ b.Property("BackChannelLogoutSessionRequired")
+ .HasColumnType("boolean");
+
+ b.Property("BackChannelLogoutUri")
+ .HasColumnType("text");
+
+ b.Property("ClientClaimsPrefix")
+ .HasColumnType("text");
+
+ b.Property("ClientId")
+ .HasColumnType("text");
+
+ b.Property("ClientName")
+ .HasColumnType("text");
+
+ b.Property("ClientUri")
+ .HasColumnType("text");
+
+ b.Property("ConsentLifetime")
+ .HasColumnType("integer");
+
+ b.Property("Created")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("DeviceCodeLifetime")
+ .HasColumnType("integer");
+
+ b.Property("EnableLocalLogin")
+ .HasColumnType("boolean");
+
+ b.Property("Enabled")
+ .HasColumnType("boolean");
+
+ b.Property("FrontChannelLogoutSessionRequired")
+ .HasColumnType("boolean");
+
+ b.Property("FrontChannelLogoutUri")
+ .HasColumnType("text");
+
+ b.Property("IdentityTokenLifetime")
+ .HasColumnType("integer");
+
+ b.Property("IncludeJwtId")
+ .HasColumnType("boolean");
+
+ b.Property("LastAccessed")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("LogoUri")
+ .HasColumnType("text");
+
+ b.Property("NonEditable")
+ .HasColumnType("boolean");
+
+ b.Property("PairWiseSubjectSalt")
+ .HasColumnType("text");
+
+ b.Property("ProtocolType")
+ .HasColumnType("text");
+
+ b.Property("RefreshTokenExpiration")
+ .HasColumnType("integer");
+
+ b.Property("RefreshTokenUsage")
+ .HasColumnType("integer");
+
+ b.Property("RequireClientSecret")
+ .HasColumnType("boolean");
+
+ b.Property("RequireConsent")
+ .HasColumnType("boolean");
+
+ b.Property("RequirePkce")
+ .HasColumnType("boolean");
+
+ b.Property("RequireRequestObject")
+ .HasColumnType("boolean");
+
+ b.Property("SlidingRefreshTokenLifetime")
+ .HasColumnType("integer");
+
+ b.Property("UpdateAccessTokenClaimsOnRefresh")
+ .HasColumnType("boolean");
+
+ b.Property("Updated")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("UserCodeType")
+ .HasColumnType("text");
+
+ b.Property("UserSsoLifetime")
+ .HasColumnType("integer");
+
+ b.HasKey("Id");
+
+ b.ToTable("Clients");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
+
+ b.Property("ClientId")
+ .HasColumnType("integer");
+
+ b.Property("Type")
+ .HasColumnType("text");
+
+ b.Property("Value")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ClientId");
+
+ b.ToTable("ClientClaims");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientCorsOrigin", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
+
+ b.Property("ClientId")
+ .HasColumnType("integer");
+
+ b.Property("ClientId1")
+ .HasColumnType("integer");
+
+ b.Property("Origin")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ClientId");
+
+ b.HasIndex("ClientId1");
+
+ b.ToTable("ClientCorsOrigins");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientGrantType", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
+
+ b.Property("ClientId")
+ .HasColumnType("integer");
+
+ b.Property("GrantType")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ClientId");
+
+ b.ToTable("ClientGrantTypes");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientIdPRestriction", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
+
+ b.Property("ClientId")
+ .HasColumnType("integer");
+
+ b.Property("ClientId1")
+ .HasColumnType("integer");
+
+ b.Property("Provider")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ClientId");
+
+ b.HasIndex("ClientId1");
+
+ b.ToTable("ClientIdPRestrictions");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientPostLogoutRedirectUri", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
+
+ b.Property("ClientId")
+ .HasColumnType("integer");
+
+ b.Property("ClientId1")
+ .HasColumnType("integer");
+
+ b.Property("PostLogoutRedirectUri")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ClientId");
+
+ b.HasIndex("ClientId1");
+
+ b.ToTable("ClientPostLogoutRedirectUris");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientProperty", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
+
+ b.Property("ClientId")
+ .HasColumnType("integer");
+
+ b.Property("ClientId1")
+ .HasColumnType("integer");
+
+ b.Property("Key")
+ .HasColumnType("text");
+
+ b.Property("Value")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ClientId");
+
+ b.HasIndex("ClientId1");
+
+ b.ToTable("ClientProperties");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientRedirectUri", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
+
+ b.Property("ClientId")
+ .HasColumnType("integer");
+
+ b.Property("RedirectUri")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ClientId");
+
+ b.ToTable("ClientRedirectUris");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientScope", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
+
+ b.Property("ClientId")
+ .HasColumnType("integer");
+
+ b.Property("Scope")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ClientId");
+
+ b.ToTable("ClientScopes");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.ClientSecret", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityAlwaysColumn(b.Property("Id"));
+
+ b.Property("ClientId")
+ .HasColumnType("integer");
+
+ b.Property("ClientId1")
+ .HasColumnType("integer");
+
+ b.Property("Created")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Expiration")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Type")
+ .HasColumnType("text");
+
+ b.Property("Value")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ClientId");
+
+ b.HasIndex("ClientId1");
+
+ b.ToTable("ClientSecrets");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.DeviceFlowCodes", b =>
+ {
+ b.Property("UserCode")
+ .HasColumnType("text");
+
+ b.Property("DeviceCode")
+ .HasColumnType("text");
+
+ b.Property("ClientId")
+ .HasColumnType("text");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Data")
+ .HasColumnType("text");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Expiration")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SessionId")
+ .HasColumnType("text");
+
+ b.Property("SubjectId")
+ .HasColumnType("text");
+
+ b.HasKey("UserCode", "DeviceCode");
+
+ b.ToTable("DeviceFlowCodes");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResource", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("Created")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("DisplayName")
+ .HasColumnType("text");
+
+ b.Property("Emphasize")
+ .HasColumnType("boolean");
+
+ b.Property("Enabled")
+ .HasColumnType("boolean");
+
+ b.Property("Name")
+ .HasColumnType("text");
+
+ b.Property("NonEditable")
+ .HasColumnType("boolean");
+
+ b.Property("Required")
+ .HasColumnType("boolean");
+
+ b.Property("ShowInDiscoveryDocument")
+ .HasColumnType("boolean");
+
+ b.Property("Updated")
+ .HasColumnType("timestamp with time zone");
+
+ b.HasKey("Id");
+
+ b.ToTable("IdentityResources");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("IdentityResourceId")
+ .HasColumnType("integer");
+
+ b.Property("Type")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("IdentityResourceId");
+
+ b.ToTable("IdentityResourceClaims");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.IdentityResourceProperty", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("IdentityResourceId")
+ .HasColumnType("integer");
+
+ b.Property("Key")
+ .HasColumnType("text");
+
+ b.Property("Value")
+ .HasColumnType("text");
+
+ b.HasKey("Id");
+
+ b.HasIndex("IdentityResourceId");
+
+ b.ToTable("IdentityResourceProperties");
+ });
+
+ modelBuilder.Entity("IdentityServer8.EntityFramework.Entities.PersistedGrant", b =>
+ {
+ b.Property("Key")
+ .HasColumnType("text");
+
+ b.Property("ClientId")
+ .HasColumnType("text");
+
+ b.Property("ConsumedTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("CreationTime")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("Data")
+ .HasColumnType("text");
+
+ b.Property("Description")
+ .HasColumnType("text");
+
+ b.Property("Expiration")
+ .HasColumnType("timestamp with time zone");
+
+ b.Property("SessionId")
+ .HasColumnType("text");
+
+ b.Property("SubjectId")
+ .HasColumnType("text");
+
+ b.Property("Type")
+ .HasColumnType("text");
+
+ b.HasKey("Key");
+
+ b.ToTable("PersistedGrants");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
+ {
+ b.Property("Id")
+ .HasColumnType("text");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("text");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("character varying(256)");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("integer");
+
+ NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id"));
+
+ b.Property("ClaimType")
+ .HasColumnType("text");
+
+ b.Property("ClaimValue")
+ .HasColumnType("text");
+
+ b.Property