The Estimate is validated
Some checks failed
Dotnet build and test / build (pull_request) Failing after 7m28s

This commit is contained in:
Paul Schneider 2026-09-13 23:29:06 +01:00
commit 2484876c5b
Signed by: notazof
GPG key ID: 1DD5D838E5343B06
20 changed files with 5392 additions and 218 deletions

27
.vscode/launch.json vendored
View file

@ -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",

19
.vscode/tasks.json vendored
View file

@ -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"
}
}
]
}

View file

@ -1,5 +0,0 @@
# Read me
## Note aux icones
㝉®🅬⛒⛑🩎🩺🞫🞮🞕🞖🞆🔴🔵🔲🖂🔧🔩🔐🔌💾💼💬💭👿👾🏷🎯🏹🌍🎎💩

View file

@ -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; }

View file

@ -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;
/// <summary>
/// True when the in-memory state has drifted from the last
/// <see cref="Load"/> or <see cref="Save"/> snapshot. The
/// Settings page binds the Sauver button's <c>IsEnabled</c> to
/// this flag, so it only enables when the user has actually
/// touched something since the last load / save. Cleared by
/// <see cref="Load"/> (and by <see cref="ApplyJson"/>), set by
/// every successful setter on the four top-level mutable
/// properties and on the sub-properties of
/// <see cref="Authentication"/>.
/// </summary>
[ObservableProperty]
public partial bool IsDirty { get; private set; } = false;
/// <summary>
/// Guards every mutation of the observable state. <c>[ObservableProperty]</c>
/// generates setters that call <c>SetProperty(...)</c> which fires
/// <c>PropertyChanged</c>. Avalonia bindings consume that event on
/// the UI thread, and a stray background-thread update is exactly
/// what crashed <c>DataValidationErrors.SetErrors</c> on
/// <c>postit://callback</c> re-launches. The lock makes mutations
/// atomic; <see cref="OnPropertyChanged(PropertyChangedEventArgs)"/>
/// then marshals the notification onto the UI thread so bindings
/// observe the change on the right thread.
/// </summary>
private readonly object _mutationGate = new();
/// <summary>
/// Scopes the PostIt client always requires from the OIDC provider,
/// regardless of what the user has in their settings file.
///
/// <para>PostIt calls into the Blog API (and any other Yavsc API
/// gated by an <c>[Authorize("…Scope")]</c> 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. <c>blogs</c>) are still their choice — we only force the
/// structural ones that OIDC itself needs.</para>
/// </summary>
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;
/// <summary>
/// True when the in-memory state has drifted from the last
/// <see cref="Load"/> or <see cref="Save"/> snapshot. The
/// Settings page binds the Sauver button's <c>IsEnabled</c> to
/// this flag, so it only enables when the user has actually
/// touched something since the last load / save. Cleared by
/// <see cref="Load"/> (and by <see cref="ApplyJson"/>), set by
/// every successful setter on the four top-level mutable
/// properties and on the sub-properties of
/// <see cref="Authentication"/>.
/// </summary>
[ObservableProperty]
public partial bool IsDirty { get; private set; } = false;
/// <summary>
/// Guards every mutation of the observable state. <c>[ObservableProperty]</c>
/// generates setters that call <c>SetProperty(...)</c> which fires
/// <c>PropertyChanged</c>. Avalonia bindings consume that event on
/// the UI thread, and a stray background-thread update is exactly
/// what crashed <c>DataValidationErrors.SetErrors</c> on
/// <c>postit://callback</c> re-launches. The lock makes mutations
/// atomic; <see cref="OnPropertyChanged(PropertyChangedEventArgs)"/>
/// then marshals the notification onto the UI thread so bindings
/// observe the change on the right thread.
/// </summary>
private readonly object _mutationGate = new();
/// <summary>
/// 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();
}
/// <summary>
/// Scopes the PostIt client always requires from the OIDC provider,
/// regardless of what the user has in their settings file.
///
/// <para>PostIt calls into the Blog API (and any other Yavsc API
/// gated by an <c>[Authorize("…Scope")]</c> 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. <c>blogs</c>) are still their choice — we only force the
/// structural ones that OIDC itself needs.</para>
/// </summary>
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"
};
/// <summary>
/// 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)
{

View file

@ -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"
]
}

View file

@ -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/"
}

View file

@ -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": {}
}

View file

@ -114,7 +114,7 @@ public sealed class BillingControllerTests : IClassFixture<ApiWebServerFixture>
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
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"});

View file

@ -101,7 +101,7 @@ public sealed class EstimateApiControllerTests : IClassFixture<ApiWebServerFixtu
var response = await http.PostAsJsonAsync(
"/api/v1/estimate",
NewEstimatePayload(commandId, clientId: "bob", ownerId: "alice"),
NewEstimatePayload(commandId, clientId: "bob"),
TestContext.Current.CancellationToken);
var body = await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken);

View file

@ -8,6 +8,7 @@ using Npgsql;
using Yavsc.Controllers;
using Yavsc.Interfaces.Workflow;
using Yavsc.Models;
using Yavsc.Models.Billing;
using Yavsc.Models.Google.Messaging;
using Yavsc.Models.Haircut;
using Yavsc.Models.Messaging;
@ -38,7 +39,8 @@ public sealed class ApiWebServerFixture : WebHostFixture
{
var npgsqlConnectionString = EnsureNpgsqlDatabaseCreated();
builder.Services.AddDbContext<ApplicationDbContext>(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<ApplicationDbContext>();
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<UserActivity>().RemoveRange(db.Set<UserActivity>());
db.Set<Activity>().RemoveRange(db.Set<Activity>());
db.Set<PerformerProfile>().RemoveRange(db.Set<PerformerProfile>());
db.Set<ApplicationUser>().RemoveRange(db.Set<ApplicationUser>());
db.Set<Location>().RemoveRange(db.Set<Location>());
db.Set<RdvQuery>().RemoveRange(db.Set<RdvQuery>());
db.Set<HairCutQuery>().RemoveRange(db.Set<HairCutQuery>());
db.Set<HairMultiCutQuery>().RemoveRange(db.Set<HairMultiCutQuery>());
db.Set<HairPrestation>().RemoveRange(db.Set<HairPrestation>());
db.Set<HairPrestationCollectionItem>().RemoveRange(db.Set<HairPrestationCollectionItem>());
// 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<CommandLine>().RemoveRange(db.Set<CommandLine>());
db.Set<Estimate>().RemoveRange(db.Set<Estimate>());
db.Set<RdvQuery>().RemoveRange(db.Set<RdvQuery>());
db.Set<HairCutQuery>().RemoveRange(db.Set<HairCutQuery>());
db.Set<HairMultiCutQuery>().RemoveRange(db.Set<HairMultiCutQuery>());
db.Set<HairPrestationCollectionItem>().RemoveRange(db.Set<HairPrestationCollectionItem>());
db.Set<HairPrestation>().RemoveRange(db.Set<HairPrestation>());
db.Set<UserActivity>().RemoveRange(db.Set<UserActivity>());
db.Set<PerformerProfile>().RemoveRange(db.Set<PerformerProfile>());
db.Set<Activity>().RemoveRange(db.Set<Activity>());
db.Set<ApplicationUser>().RemoveRange(db.Set<ApplicationUser>());
db.Set<Location>().RemoveRange(db.Set<Location>());
db.SaveChanges();
return;
}
@ -321,72 +340,6 @@ public sealed class ApiWebServerFixture : WebHostFixture
db.Database.EnsureDeleted();
}
private static IReadOnlyList<IEntityType> 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<IEntityType>(entityTypes);
var dependencies = new Dictionary<IEntityType, HashSet<IEntityType>>();
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<IEntityType>(
dependencies.Where(kvp => kvp.Value.Count == 0).Select(kvp => kvp.Key));
var order = new List<IEntityType>(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();

View file

@ -125,7 +125,7 @@ namespace Yavsc.Controllers
if (estimate.CommandId != null)
{
var query = _context.Set<NominativeServiceCommand>()
var query = _context.NominativeServiceCommands
.FirstOrDefault(q => q.Id == estimate.CommandId);
if (query == null)
{

View file

@ -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"
}
}
}
}

View file

@ -1,6 +1,7 @@
{
"Site": {
"Authority": "https://localhost:5001",
"Audience": ["api"],
"CorsAllowedOrigins": [
"https://localhost:5003",
"https://yavsc.pschneider.fr"

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,423 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Yavsc.Migrations
{
/// <inheritdoc />
public partial class NominativeServiceCommand : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Estimates_NominativeServiceCommand_CommandId",
table: "Estimates");
migrationBuilder.DropForeignKey(
name: "FK_HairPrestationCollectionItem_NominativeServiceCommand_Query~",
table: "HairPrestationCollectionItem");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_Activities_ActivityCode",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_AspNetUsers_ClientId",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_BrusherProfile_SelectedProfileUser~",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_GitRepositoryReference_GitId",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_HairPrestation_PrestationId",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_Locations_HairMultiCutQuery_Locati~",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_Locations_RdvQuery_LocationId",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_PayPalPayment_PaymentId",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommand_Performers_PerformerId",
table: "NominativeServiceCommand");
migrationBuilder.DropForeignKey(
name: "FK_ProjectBuildConfiguration_NominativeServiceCommand_ProjectId",
table: "ProjectBuildConfiguration");
migrationBuilder.DropPrimaryKey(
name: "PK_NominativeServiceCommand",
table: "NominativeServiceCommand");
migrationBuilder.RenameTable(
name: "NominativeServiceCommand",
newName: "NominativeServiceCommands");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_SelectedProfileUserId",
table: "NominativeServiceCommands",
newName: "IX_NominativeServiceCommands_SelectedProfileUserId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_RdvQuery_LocationId",
table: "NominativeServiceCommands",
newName: "IX_NominativeServiceCommands_RdvQuery_LocationId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_PrestationId",
table: "NominativeServiceCommands",
newName: "IX_NominativeServiceCommands_PrestationId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_PerformerId",
table: "NominativeServiceCommands",
newName: "IX_NominativeServiceCommands_PerformerId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_PaymentId",
table: "NominativeServiceCommands",
newName: "IX_NominativeServiceCommands_PaymentId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_HairMultiCutQuery_LocationId",
table: "NominativeServiceCommands",
newName: "IX_NominativeServiceCommands_HairMultiCutQuery_LocationId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_GitId",
table: "NominativeServiceCommands",
newName: "IX_NominativeServiceCommands_GitId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_ClientId",
table: "NominativeServiceCommands",
newName: "IX_NominativeServiceCommands_ClientId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommand_ActivityCode",
table: "NominativeServiceCommands",
newName: "IX_NominativeServiceCommands_ActivityCode");
migrationBuilder.AddPrimaryKey(
name: "PK_NominativeServiceCommands",
table: "NominativeServiceCommands",
column: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Estimates_NominativeServiceCommands_CommandId",
table: "Estimates",
column: "CommandId",
principalTable: "NominativeServiceCommands",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_HairPrestationCollectionItem_NominativeServiceCommands_Quer~",
table: "HairPrestationCollectionItem",
column: "QueryId",
principalTable: "NominativeServiceCommands",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommands_Activities_ActivityCode",
table: "NominativeServiceCommands",
column: "ActivityCode",
principalTable: "Activities",
principalColumn: "Code",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommands_AspNetUsers_ClientId",
table: "NominativeServiceCommands",
column: "ClientId",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommands_BrusherProfile_SelectedProfileUse~",
table: "NominativeServiceCommands",
column: "SelectedProfileUserId",
principalTable: "BrusherProfile",
principalColumn: "UserId");
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommands_GitRepositoryReference_GitId",
table: "NominativeServiceCommands",
column: "GitId",
principalTable: "GitRepositoryReference",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommands_HairPrestation_PrestationId",
table: "NominativeServiceCommands",
column: "PrestationId",
principalTable: "HairPrestation",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommands_Locations_HairMultiCutQuery_Locat~",
table: "NominativeServiceCommands",
column: "HairMultiCutQuery_LocationId",
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommands_Locations_RdvQuery_LocationId",
table: "NominativeServiceCommands",
column: "RdvQuery_LocationId",
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommands_PayPalPayment_PaymentId",
table: "NominativeServiceCommands",
column: "PaymentId",
principalTable: "PayPalPayment",
principalColumn: "CreationToken");
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommands_Performers_PerformerId",
table: "NominativeServiceCommands",
column: "PerformerId",
principalTable: "Performers",
principalColumn: "PerformerId",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_ProjectBuildConfiguration_NominativeServiceCommands_Project~",
table: "ProjectBuildConfiguration",
column: "ProjectId",
principalTable: "NominativeServiceCommands",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Estimates_NominativeServiceCommands_CommandId",
table: "Estimates");
migrationBuilder.DropForeignKey(
name: "FK_HairPrestationCollectionItem_NominativeServiceCommands_Quer~",
table: "HairPrestationCollectionItem");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommands_Activities_ActivityCode",
table: "NominativeServiceCommands");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommands_AspNetUsers_ClientId",
table: "NominativeServiceCommands");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommands_BrusherProfile_SelectedProfileUse~",
table: "NominativeServiceCommands");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommands_GitRepositoryReference_GitId",
table: "NominativeServiceCommands");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommands_HairPrestation_PrestationId",
table: "NominativeServiceCommands");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommands_Locations_HairMultiCutQuery_Locat~",
table: "NominativeServiceCommands");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommands_Locations_LocationId",
table: "NominativeServiceCommands");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommands_Locations_RdvQuery_LocationId",
table: "NominativeServiceCommands");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommands_PayPalPayment_PaymentId",
table: "NominativeServiceCommands");
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommands_Performers_PerformerId",
table: "NominativeServiceCommands");
migrationBuilder.DropForeignKey(
name: "FK_ProjectBuildConfiguration_NominativeServiceCommands_Project~",
table: "ProjectBuildConfiguration");
migrationBuilder.DropPrimaryKey(
name: "PK_NominativeServiceCommands",
table: "NominativeServiceCommands");
migrationBuilder.RenameTable(
name: "NominativeServiceCommands",
newName: "NominativeServiceCommand");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommands_SelectedProfileUserId",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_SelectedProfileUserId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommands_RdvQuery_LocationId",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_RdvQuery_LocationId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommands_PrestationId",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_PrestationId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommands_PerformerId",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_PerformerId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommands_PaymentId",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_PaymentId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommands_HairMultiCutQuery_LocationId",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_HairMultiCutQuery_LocationId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommands_GitId",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_GitId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommands_ClientId",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_ClientId");
migrationBuilder.RenameIndex(
name: "IX_NominativeServiceCommands_ActivityCode",
table: "NominativeServiceCommand",
newName: "IX_NominativeServiceCommand_ActivityCode");
migrationBuilder.AddPrimaryKey(
name: "PK_NominativeServiceCommand",
table: "NominativeServiceCommand",
column: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Estimates_NominativeServiceCommand_CommandId",
table: "Estimates",
column: "CommandId",
principalTable: "NominativeServiceCommand",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_HairPrestationCollectionItem_NominativeServiceCommand_Query~",
table: "HairPrestationCollectionItem",
column: "QueryId",
principalTable: "NominativeServiceCommand",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_Activities_ActivityCode",
table: "NominativeServiceCommand",
column: "ActivityCode",
principalTable: "Activities",
principalColumn: "Code",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_AspNetUsers_ClientId",
table: "NominativeServiceCommand",
column: "ClientId",
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_BrusherProfile_SelectedProfileUser~",
table: "NominativeServiceCommand",
column: "SelectedProfileUserId",
principalTable: "BrusherProfile",
principalColumn: "UserId");
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_GitRepositoryReference_GitId",
table: "NominativeServiceCommand",
column: "GitId",
principalTable: "GitRepositoryReference",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_HairPrestation_PrestationId",
table: "NominativeServiceCommand",
column: "PrestationId",
principalTable: "HairPrestation",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_Locations_HairMultiCutQuery_Locati~",
table: "NominativeServiceCommand",
column: "HairMultiCutQuery_LocationId",
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_Locations_RdvQuery_LocationId",
table: "NominativeServiceCommand",
column: "RdvQuery_LocationId",
principalTable: "Locations",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_PayPalPayment_PaymentId",
table: "NominativeServiceCommand",
column: "PaymentId",
principalTable: "PayPalPayment",
principalColumn: "CreationToken");
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommand_Performers_PerformerId",
table: "NominativeServiceCommand",
column: "PerformerId",
principalTable: "Performers",
principalColumn: "PerformerId",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_ProjectBuildConfiguration_NominativeServiceCommand_ProjectId",
table: "ProjectBuildConfiguration",
column: "ProjectId",
principalTable: "NominativeServiceCommand",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
}

View file

@ -0,0 +1,49 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Yavsc.Models;
#nullable disable
namespace Yavsc.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20260913220000_AddHairCutQueryLocationId")]
public partial class AddHairCutQueryLocationId : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<long>(
name: "LocationId",
table: "NominativeServiceCommands",
type: "bigint",
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_NominativeServiceCommands_LocationId",
table: "NominativeServiceCommands",
column: "LocationId");
migrationBuilder.AddForeignKey(
name: "FK_NominativeServiceCommands_Locations_LocationId",
table: "NominativeServiceCommands",
column: "LocationId",
principalTable: "Locations",
principalColumn: "Id");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_NominativeServiceCommands_Locations_LocationId",
table: "NominativeServiceCommands");
migrationBuilder.DropIndex(
name: "IX_NominativeServiceCommands_LocationId",
table: "NominativeServiceCommands");
migrationBuilder.DropColumn(
name: "LocationId",
table: "NominativeServiceCommands");
}
}
}

View file

@ -1495,7 +1495,7 @@ namespace Yavsc.Migrations
b.HasIndex("PerformerId");
b.ToTable("NominativeServiceCommand");
b.ToTable("NominativeServiceCommands");
b.HasDiscriminator<string>("Discriminator").HasValue("NominativeServiceCommand");
@ -3336,7 +3336,7 @@ namespace Yavsc.Migrations
b.HasIndex("LocationId");
b.ToTable("NominativeServiceCommand", t =>
b.ToTable("NominativeServiceCommands", t =>
{
t.Property("EventDate")
.HasColumnName("HairMultiCutQuery_EventDate");
@ -3367,7 +3367,7 @@ namespace Yavsc.Migrations
b.HasIndex("LocationId");
b.ToTable("NominativeServiceCommand", t =>
b.ToTable("NominativeServiceCommands", t =>
{
t.Property("EventDate")
.HasColumnName("RdvQuery_EventDate");

View file

@ -16,7 +16,7 @@
"Slogan": "Yavsc!",
"StyleSheet": "/css/default.css",
"Authority": "https://[Your domaine name]",
"Audience": ["blogs"],
"Audience": ["blogs", "api"],
"ExternalUrl": "https://[Your domaine name]",
"ApiUrl": "https://[Your API domaine name]",
"CorsAllowedOrigins": [

View file

@ -507,5 +507,7 @@ namespace Yavsc.Models
public DbSet<RegexAlertPattern> RegexAlertPatterns { get; set; }
public DbSet<ModerationLog> ModerationLogs { get; set; }
public DbSet<NominativeServiceCommand> NominativeServiceCommands { get; set; }
}
}