feat/files-control #52

Merged
notazof merged 38 commits from feat/files-control into main 2026-09-14 10:20:35 +01:00
20 changed files with 5392 additions and 218 deletions
Showing only changes of commit 2484876c5b - Show all commits

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

Paul Schneider 2026-09-13 23:29:06 +01:00
Signed by: notazof
GPG key ID: 1DD5D838E5343B06

27
.vscode/launch.json vendored
View file

@ -4,6 +4,22 @@
// Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387 // Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "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", "name": "Android Debug",
"type": "mono", "type": "mono",
@ -20,10 +36,10 @@
"port": 55555 "port": 55555
}, },
{ {
"name": "API", "name": "Yavsc API",
"type": "dotnet", "type": "dotnet",
"request": "launch", "request": "launch",
"projectPath": "${workspaceFolder}/src/Api/Api.csproj" "projectPath": "${workspaceFolder}/src/Yavsc.Api/Yavsc.Api.csproj"
}, },
{ {
"name": "Yavsc Org", "name": "Yavsc Org",
@ -37,12 +53,7 @@
"request": "launch", "request": "launch",
"projectPath": "${workspaceFolder}/src/Yavsc.Blogs/Yavsc.Blogs.csproj" "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)", "name": "Test PostIt.Android launch (Xamarin.UITest)",
"type": "coreclr", "type": "coreclr",

19
.vscode/tasks.json vendored
View file

@ -69,7 +69,7 @@
{ {
"label": "test api backend (npgsql)", "label": "test api backend (npgsql)",
"type": "process", "type": "process",
"problemMatcher": ["$msCompile"], "problemMatcher": "$msCompile",
"command": "dotnet", "command": "dotnet",
"args": [ "args": [
"test", "test",
@ -80,7 +80,7 @@
"options": { "options": {
"cwd": "src/Yavsc.Api.Test", "cwd": "src/Yavsc.Api.Test",
"env": { "env": {
"YAVSC_API_TEST_DB_PROVIDER": "npgsql", "YAVSC_API_TEST_DB_PROVIDER": "npgsql"
} }
}, },
"group": { "group": {
@ -101,6 +101,21 @@
"kind": "build" "kind": "build"
}, },
"isBackground": true "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 const string DefaultClientId = "postit";
public static readonly string[] DefaultScopes = { "blogs" }; public static readonly string[] DefaultScopes = { "blogs", "api" };
[ObservableProperty] [ObservableProperty]
public partial string Authority { get; set; } public partial string Authority { get; set; }

View file

@ -15,7 +15,8 @@ namespace PostIt.ViewModels;
public partial class Settings : ViewModelBase public partial class Settings : ViewModelBase
{ {
public string SettingsFileName {get; private set;} = "postit-settings.json"; [JsonIgnore]
public string? SettingsFileFullName { get; private set; }
[ObservableProperty] [ObservableProperty]
public partial AuthenticationSettings Authentication { get; set; } = new(); public partial AuthenticationSettings Authentication { get; set; } = new();
@ -39,6 +40,60 @@ public partial class Settings : ViewModelBase
[JsonIgnore] [JsonIgnore]
public partial StatusNotice ActionStatus { get; set; } = StatusNotice.Info("Pret."); 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) public void SetActionStatus(string message, StatusSeverity severity = StatusSeverity.Info)
{ {
ActionStatus = severity switch ActionStatus = severity switch
@ -86,35 +141,6 @@ public partial class Settings : ViewModelBase
MarkDirty(); 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> /// <summary>
/// Build OidcClient options configured for Authorization Code + PKCE /// Build OidcClient options configured for Authorization Code + PKCE
/// (no client secret). The browser implementation should be supplied /// (no client secret). The browser implementation should be supplied
@ -178,27 +204,6 @@ public partial class Settings : ViewModelBase
Authentication.RefreshScopeListText(); 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> /// <summary>
/// Merge user-configured scopes with the built-in ones. User scopes /// Merge user-configured scopes with the built-in ones. User scopes
@ -255,7 +260,14 @@ public partial class Settings : ViewModelBase
&& !string.IsNullOrWhiteSpace(envJson)) && !string.IsNullOrWhiteSpace(envJson))
{ {
Console.WriteLine("🔎 Loading settings from POSTIT_SETTINGS_JSON environment variable."); 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; Loaded = true;
return; return;
} }
@ -264,9 +276,22 @@ public partial class Settings : ViewModelBase
"PostIt" "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) if (!configFileInfo.Exists)
{ {
@ -295,6 +320,7 @@ public partial class Settings : ViewModelBase
using var reader = new StreamReader(stream); using var reader = new StreamReader(stream);
var json = reader.ReadToEnd(); var json = reader.ReadToEnd();
ApplyJson(json, $"user file {configFileInfo.FullName}"); ApplyJson(json, $"user file {configFileInfo.FullName}");
SettingsFileFullName = configFileInfo.FullName;
Loaded = true; Loaded = true;
} }
catch (Exception ex) catch (Exception ex)
@ -458,11 +484,17 @@ public partial class Settings : ViewModelBase
{ {
SetActionStatus("Enregistrement des parametres...", StatusSeverity.Info); SetActionStatus("Enregistrement des parametres...", StatusSeverity.Info);
var configDir = Path.Combine( if (SettingsFileFullName is null)
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), {
"PostIt"); var configDir = Path.Combine(
Directory.CreateDirectory(configDir); Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
var configPath = Path.Combine(configDir, SettingsFileName); "PostIt");
Directory.CreateDirectory(configDir);
SettingsFileFullName = Path.Combine(configDir, DEFAULT_SETTINGS_FILENAME);
}
var configPath = SettingsFileFullName!;
Directory.CreateDirectory(Path.GetDirectoryName(configPath)!);
lock (_mutationGate) 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": { "Authentication": {
"ClientId": "postit", "Authority": "https://localhost:5001",
"Authority": "https://yavsc.pschneider.fr/" "ClientId": "postit",
},
"RedirectUri": "postit://callback",
"DarkMode": true,
"ApiUrl": "https://api.pschneider.fr/api/v1/",
"Scopes": [ "Scopes": [
"openid", "blogs",
"profile", "api"
"offline_access", ],
"blogs" "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>(); var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
db.Database.ExecuteSqlInterpolated($@" db.Database.ExecuteSqlInterpolated($@"
INSERT INTO ""NominativeServiceCommand"" INSERT INTO ""NominativeServiceCommands""
(""ActivityCode"", ""ClientId"", ""Consent"", ""DateCreated"", ""DateModified"", ""Description"", ""Discriminator"", ""PerformerId"", ""Status"", ""UserCreated"", ""UserModified"") (""ActivityCode"", ""ClientId"", ""Consent"", ""DateCreated"", ""DateModified"", ""Description"", ""Discriminator"", ""PerformerId"", ""Status"", ""UserCreated"", ""UserModified"")
VALUES VALUES
({"dev"}, {"bob"}, {true}, {DateTime.UtcNow.AddMinutes(-5)}, {DateTime.UtcNow.AddMinutes(-4)}, {"Legacy malformed row"}, {""}, {"alice"}, {(int)QueryStatus.Accepted}, {"alice"}, {"alice"}); ({"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( var response = await http.PostAsJsonAsync(
"/api/v1/estimate", "/api/v1/estimate",
NewEstimatePayload(commandId, clientId: "bob", ownerId: "alice"), NewEstimatePayload(commandId, clientId: "bob"),
TestContext.Current.CancellationToken); TestContext.Current.CancellationToken);
var body = await response.Content.ReadAsStringAsync(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.Controllers;
using Yavsc.Interfaces.Workflow; using Yavsc.Interfaces.Workflow;
using Yavsc.Models; using Yavsc.Models;
using Yavsc.Models.Billing;
using Yavsc.Models.Google.Messaging; using Yavsc.Models.Google.Messaging;
using Yavsc.Models.Haircut; using Yavsc.Models.Haircut;
using Yavsc.Models.Messaging; using Yavsc.Models.Messaging;
@ -38,7 +39,8 @@ public sealed class ApiWebServerFixture : WebHostFixture
{ {
var npgsqlConnectionString = EnsureNpgsqlDatabaseCreated(); var npgsqlConnectionString = EnsureNpgsqlDatabaseCreated();
builder.Services.AddDbContext<ApplicationDbContext>(opt => builder.Services.AddDbContext<ApplicationDbContext>(opt =>
opt.UseNpgsql(npgsqlConnectionString)); opt.UseNpgsql(npgsqlConnectionString,
x => x.MigrationsAssembly("Yavsc.Org")));
} }
else else
{ {
@ -99,7 +101,18 @@ public sealed class ApiWebServerFixture : WebHostFixture
using (var scope = app.Services.CreateScope()) using (var scope = app.Services.CreateScope())
{ {
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); 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; await Task.CompletedTask;
@ -303,17 +316,23 @@ public sealed class ApiWebServerFixture : WebHostFixture
{ {
if (UseNpgsqlProvider()) if (UseNpgsqlProvider())
{ {
db.Set<UserActivity>().RemoveRange(db.Set<UserActivity>()); // Purge only the tables of the test graph, children before
db.Set<Activity>().RemoveRange(db.Set<Activity>()); // parents, so no DELETE violates a foreign key. Estimate and
db.Set<PerformerProfile>().RemoveRange(db.Set<PerformerProfile>()); // CommandLine reference NominativeServiceCommand (CommandId) and
db.Set<ApplicationUser>().RemoveRange(db.Set<ApplicationUser>()); // must be deleted before the Rdv/HairCut/HairMultiCut queries.
db.Set<Location>().RemoveRange(db.Set<Location>()); db.Set<CommandLine>().RemoveRange(db.Set<CommandLine>());
db.Set<RdvQuery>().RemoveRange(db.Set<RdvQuery>()); db.Set<Estimate>().RemoveRange(db.Set<Estimate>());
db.Set<HairCutQuery>().RemoveRange(db.Set<HairCutQuery>()); db.Set<RdvQuery>().RemoveRange(db.Set<RdvQuery>());
db.Set<HairMultiCutQuery>().RemoveRange(db.Set<HairMultiCutQuery>()); db.Set<HairCutQuery>().RemoveRange(db.Set<HairCutQuery>());
db.Set<HairPrestation>().RemoveRange(db.Set<HairPrestation>()); db.Set<HairMultiCutQuery>().RemoveRange(db.Set<HairMultiCutQuery>());
db.Set<HairPrestationCollectionItem>().RemoveRange(db.Set<HairPrestationCollectionItem>()); 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(); db.SaveChanges();
return; return;
} }
@ -321,72 +340,6 @@ public sealed class ApiWebServerFixture : WebHostFixture
db.Database.EnsureDeleted(); 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() public void ResetAndSeedRdvQueryGraph()
{ {
ResetAndSeedActivityGraph(); ResetAndSeedActivityGraph();

View file

@ -125,7 +125,7 @@ namespace Yavsc.Controllers
if (estimate.CommandId != null) if (estimate.CommandId != null)
{ {
var query = _context.Set<NominativeServiceCommand>() var query = _context.NominativeServiceCommands
.FirstOrDefault(q => q.Id == estimate.CommandId); .FirstOrDefault(q => q.Id == estimate.CommandId);
if (query == null) 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": { "Site": {
"Authority": "https://localhost:5001", "Authority": "https://localhost:5001",
"Audience": ["api"],
"CorsAllowedOrigins": [ "CorsAllowedOrigins": [
"https://localhost:5003", "https://localhost:5003",
"https://yavsc.pschneider.fr" "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.HasIndex("PerformerId");
b.ToTable("NominativeServiceCommand"); b.ToTable("NominativeServiceCommands");
b.HasDiscriminator<string>("Discriminator").HasValue("NominativeServiceCommand"); b.HasDiscriminator<string>("Discriminator").HasValue("NominativeServiceCommand");
@ -3336,7 +3336,7 @@ namespace Yavsc.Migrations
b.HasIndex("LocationId"); b.HasIndex("LocationId");
b.ToTable("NominativeServiceCommand", t => b.ToTable("NominativeServiceCommands", t =>
{ {
t.Property("EventDate") t.Property("EventDate")
.HasColumnName("HairMultiCutQuery_EventDate"); .HasColumnName("HairMultiCutQuery_EventDate");
@ -3367,7 +3367,7 @@ namespace Yavsc.Migrations
b.HasIndex("LocationId"); b.HasIndex("LocationId");
b.ToTable("NominativeServiceCommand", t => b.ToTable("NominativeServiceCommands", t =>
{ {
t.Property("EventDate") t.Property("EventDate")
.HasColumnName("RdvQuery_EventDate"); .HasColumnName("RdvQuery_EventDate");

View file

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

View file

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