Identity reloaded
This commit is contained in:
parent
ef0f5ddcac
commit
333b066e66
33 changed files with 195 additions and 180 deletions
|
|
@ -13,9 +13,9 @@
|
||||||
-->
|
-->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="coverlet.collector" Version="10.0.1" />
|
<PackageVersion Include="coverlet.collector" Version="10.0.1" />
|
||||||
<PackageVersion Include="GitVersion.MsBuild" Version="6.7.0" />
|
<PackageVersion Include="GitVersion.MsBuild" Version="6.8.1" />
|
||||||
<PackageVersion Include="HigginsSoft.IdentityServer8" Version="8.0.5-preview-net9" />
|
<PackageVersion Include="HigginsSoft.IdentityServer8" Version="8.1.0-alpha.171" />
|
||||||
<PackageVersion Include="HigginsSoft.IdentityServer8.EntityFramework" Version="8.0.5-preview-net9" />
|
<PackageVersion Include="HigginsSoft.IdentityServer8.EntityFramework" Version="8.1.0-alpha.171" />
|
||||||
<PackageVersion Include="IdentityModel.OidcClient" Version="6.0.0" />
|
<PackageVersion Include="IdentityModel.OidcClient" Version="6.0.0" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.9" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.9" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.9" />
|
<PackageVersion Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.9" />
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.9" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.9" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.9" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.9" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.9" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.9" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
|
||||||
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" />
|
<PackageVersion Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" />
|
||||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||||
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
<PackageVersion Include="xunit.v3" Version="3.2.2" />
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ COPY src/PostIt/PostIt.Desktop/*.csproj ./src/PostIt/PostIt.Desktop/
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# 3. Restauration des dépendances avec vos workloads actifs
|
# 3. Restauration des dépendances avec vos workloads actifs
|
||||||
RUN dotnet nuget add source https://isn.pschneider.fr/v3/index.json --allow-insecure-connections
|
RUN dotnet nuget add source https://isn.pschneider.fr/api/v3/index.json
|
||||||
|
|
||||||
# 4. Restauration des dépendances pour tous les projets
|
# 4. Restauration des dépendances pour tous les projets
|
||||||
RUN dotnet restore
|
RUN dotnet restore
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -11,7 +11,7 @@ all:
|
||||||
dotnet build --nologo
|
dotnet build --nologo
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
dotnet clean
|
dotnet clean -c $(CONFIG)
|
||||||
|
|
||||||
src/Yavsc/bin/output/wwwroot:
|
src/Yavsc/bin/output/wwwroot:
|
||||||
dotnet --project src/Yavsc.Org/Yavsc.Org.csproj publish
|
dotnet --project src/Yavsc.Org/Yavsc.Org.csproj publish
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace PostIt.Tests;
|
||||||
/// URL emitted by OidcClient, extracts its <c>state</c>, and returns a
|
/// URL emitted by OidcClient, extracts its <c>state</c>, and returns a
|
||||||
/// BrowserResult that mimics the OIDC redirect-with-code callback.
|
/// BrowserResult that mimics the OIDC redirect-with-code callback.
|
||||||
///
|
///
|
||||||
/// The paired <see cref="OidcStubAuthority"/>'s token endpoint accepts
|
/// The paired <see cref="OIDCStubAuthority"/>'s token endpoint accepts
|
||||||
/// any authorization code, so we don't need to mint a real one here.
|
/// any authorization code, so we don't need to mint a real one here.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class FakeAuthorizingBrowser
|
public sealed class FakeAuthorizingBrowser
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ public class LoginPageViewModelTests
|
||||||
// short-circuits the system browser. The authority signs its
|
// short-circuits the system browser. The authority signs its
|
||||||
// access_token with RS256; the fake browser captures the redirect
|
// access_token with RS256; the fake browser captures the redirect
|
||||||
// URI so the authority can complete the token exchange.
|
// URI so the authority can complete the token exchange.
|
||||||
using var authority = await OidcStubAuthority.StartAsync();
|
using var authority = await OIDCStubAuthority.StartAsync();
|
||||||
var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri);
|
var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri);
|
||||||
|
|
||||||
var settings = new PostIt.Settings
|
var settings = new PostIt.Settings
|
||||||
|
|
@ -96,7 +96,7 @@ public class LoginPageViewModelTests
|
||||||
// double slash before /.well-known/openid-configuration. The
|
// double slash before /.well-known/openid-configuration. The
|
||||||
// stub advertises itself without the trailing slash; OidcClient
|
// stub advertises itself without the trailing slash; OidcClient
|
||||||
// must bridge.
|
// must bridge.
|
||||||
using var authority = await OidcStubAuthority.StartAsync();
|
using var authority = await OIDCStubAuthority.StartAsync();
|
||||||
var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri);
|
var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri);
|
||||||
|
|
||||||
var settings = new PostIt.Settings
|
var settings = new PostIt.Settings
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ namespace PostIt.Tests;
|
||||||
/// the browser intercepts the authorize redirect, the server completes
|
/// the browser intercepts the authorize redirect, the server completes
|
||||||
/// the token exchange.
|
/// the token exchange.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class OidcStubAuthority : IAsyncDisposable, IDisposable
|
public sealed class OIDCStubAuthority : IAsyncDisposable, IDisposable
|
||||||
{
|
{
|
||||||
private readonly HttpListener _listener;
|
private readonly HttpListener _listener;
|
||||||
private readonly RSA _rsa;
|
private readonly RSA _rsa;
|
||||||
|
|
@ -30,7 +30,7 @@ public sealed class OidcStubAuthority : IAsyncDisposable, IDisposable
|
||||||
public string Issuer { get; }
|
public string Issuer { get; }
|
||||||
public string LoopbackRedirectUri { get; }
|
public string LoopbackRedirectUri { get; }
|
||||||
|
|
||||||
private OidcStubAuthority(HttpListener listener, RSA rsa, string kid, string issuer, string loopback)
|
private OIDCStubAuthority(HttpListener listener, RSA rsa, string kid, string issuer, string loopback)
|
||||||
{
|
{
|
||||||
_listener = listener;
|
_listener = listener;
|
||||||
_rsa = rsa;
|
_rsa = rsa;
|
||||||
|
|
@ -39,7 +39,7 @@ public sealed class OidcStubAuthority : IAsyncDisposable, IDisposable
|
||||||
LoopbackRedirectUri = loopback;
|
LoopbackRedirectUri = loopback;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<OidcStubAuthority> StartAsync()
|
public static async Task<OIDCStubAuthority> StartAsync()
|
||||||
{
|
{
|
||||||
// Pick a free loopback port.
|
// Pick a free loopback port.
|
||||||
var port = GetFreePort();
|
var port = GetFreePort();
|
||||||
|
|
@ -53,7 +53,7 @@ public sealed class OidcStubAuthority : IAsyncDisposable, IDisposable
|
||||||
var rsa = RSA.Create(2048);
|
var rsa = RSA.Create(2048);
|
||||||
var kid = "test-key-1";
|
var kid = "test-key-1";
|
||||||
|
|
||||||
var authority = new OidcStubAuthority(listener, rsa, kid, prefix.TrimEnd('/'), loopback);
|
var authority = new OIDCStubAuthority(listener, rsa, kid, prefix.TrimEnd('/'), loopback);
|
||||||
_ = Task.Run(() => authority.AcceptLoopAsync(authority._cts.Token));
|
_ = Task.Run(() => authority.AcceptLoopAsync(authority._cts.Token));
|
||||||
return authority;
|
return authority;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ namespace PostIt.Tests;
|
||||||
/// End-to-end coverage of <see cref="YavscApiClient"/>: silent
|
/// End-to-end coverage of <see cref="YavscApiClient"/>: silent
|
||||||
/// refresh on a near-expiry access token, 401-driven refresh + retry,
|
/// refresh on a near-expiry access token, 401-driven refresh + retry,
|
||||||
/// and persistence of the token bundle via <see cref="TokenStore"/>.
|
/// and persistence of the token bundle via <see cref="TokenStore"/>.
|
||||||
/// Uses the project's <see cref="OidcStubAuthority"/> for the IdP and
|
/// Uses the project's <see cref="OIDCStubAuthority"/> for the IdP and
|
||||||
/// a tiny in-process HTTP listener for the API server side.
|
/// a tiny in-process HTTP listener for the API server side.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class YavscApiClientTests
|
public class YavscApiClientTests
|
||||||
|
|
@ -45,7 +45,7 @@ public class YavscApiClientTests
|
||||||
// in-memory access token as expired and re-run a call. The
|
// in-memory access token as expired and re-run a call. The
|
||||||
// refresh path must rotate the refresh token transparently
|
// refresh path must rotate the refresh token transparently
|
||||||
// and the API call must succeed with the new token.
|
// and the API call must succeed with the new token.
|
||||||
using var authority = await OidcStubAuthority.StartAsync();
|
using var authority = await OIDCStubAuthority.StartAsync();
|
||||||
using var apiServer = new StubApiServer();
|
using var apiServer = new StubApiServer();
|
||||||
await apiServer.StartAsync();
|
await apiServer.StartAsync();
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ public class YavscApiClientTests
|
||||||
var reloaded = new YavscApiClient(settings, new TokenStore(tokensPath));
|
var reloaded = new YavscApiClient(settings, new TokenStore(tokensPath));
|
||||||
|
|
||||||
var posts = await reloaded.CallAsync<List<StubApiServer.Post>>(
|
var posts = await reloaded.CallAsync<List<StubApiServer.Post>>(
|
||||||
HttpMethod.Get, "posts");
|
HttpMethod.Get, "posts", TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Assert.NotNull(posts);
|
Assert.NotNull(posts);
|
||||||
Assert.NotEmpty(posts);
|
Assert.NotEmpty(posts);
|
||||||
|
|
@ -85,7 +85,7 @@ public class YavscApiClientTests
|
||||||
{
|
{
|
||||||
// API server returns 401 on the first request, 200 on the next.
|
// API server returns 401 on the first request, 200 on the next.
|
||||||
// YavscApiClient must refresh, then retry exactly once.
|
// YavscApiClient must refresh, then retry exactly once.
|
||||||
using var authority = await OidcStubAuthority.StartAsync();
|
using var authority = await OIDCStubAuthority.StartAsync();
|
||||||
using var apiServer = new StubApiServer(forceFirstRequest: true);
|
using var apiServer = new StubApiServer(forceFirstRequest: true);
|
||||||
await apiServer.StartAsync();
|
await apiServer.StartAsync();
|
||||||
|
|
||||||
|
|
@ -97,7 +97,7 @@ public class YavscApiClientTests
|
||||||
settings, authority, tokensPath);
|
settings, authority, tokensPath);
|
||||||
|
|
||||||
var posts = await client.CallAsync<List<StubApiServer.Post>>(
|
var posts = await client.CallAsync<List<StubApiServer.Post>>(
|
||||||
HttpMethod.Get, "posts");
|
HttpMethod.Get, "posts", TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Assert.NotEmpty(posts);
|
Assert.NotEmpty(posts);
|
||||||
Assert.Equal(2, apiServer.RequestCount);
|
Assert.Equal(2, apiServer.RequestCount);
|
||||||
|
|
@ -125,14 +125,15 @@ public class YavscApiClientTests
|
||||||
var client = new YavscApiClient(settings, new TokenStore(Path.Combine(
|
var client = new YavscApiClient(settings, new TokenStore(Path.Combine(
|
||||||
Path.GetTempPath(), $"postit-tests-noop-{Guid.NewGuid():N}.json")));
|
Path.GetTempPath(), $"postit-tests-noop-{Guid.NewGuid():N}.json")));
|
||||||
|
|
||||||
await Assert.ThrowsAsync<InvalidOperationException>(() =>
|
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
client.CallAsync<JsonElement>(HttpMethod.Get, "posts"));
|
() =>
|
||||||
|
client.CallAsync<JsonElement>(HttpMethod.Get, "posts", TestContext.Current.CancellationToken));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HasValidSession_is_true_after_login()
|
public async Task HasValidSession_is_true_after_login()
|
||||||
{
|
{
|
||||||
using var authority = await OidcStubAuthority.StartAsync();
|
using var authority = await OIDCStubAuthority.StartAsync();
|
||||||
using var apiServer = new StubApiServer();
|
using var apiServer = new StubApiServer();
|
||||||
await apiServer.StartAsync();
|
await apiServer.StartAsync();
|
||||||
|
|
||||||
|
|
@ -154,7 +155,7 @@ public class YavscApiClientTests
|
||||||
|
|
||||||
// --- helpers --------------------------------------------------------
|
// --- helpers --------------------------------------------------------
|
||||||
|
|
||||||
private static PostIt.Settings BuildSettings(OidcStubAuthority authority, string apiBaseUrl) => new()
|
private static PostIt.Settings BuildSettings(OIDCStubAuthority authority, string apiBaseUrl) => new()
|
||||||
{
|
{
|
||||||
Authentication = new AuthenticationSettings
|
Authentication = new AuthenticationSettings
|
||||||
{
|
{
|
||||||
|
|
@ -167,7 +168,7 @@ public class YavscApiClientTests
|
||||||
};
|
};
|
||||||
|
|
||||||
private static async Task<YavscApiClient> LoginAndPersistAsync(
|
private static async Task<YavscApiClient> LoginAndPersistAsync(
|
||||||
PostIt.Settings settings, OidcStubAuthority authority, string tokensPath)
|
PostIt.Settings settings, OIDCStubAuthority authority, string tokensPath)
|
||||||
{
|
{
|
||||||
var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri);
|
var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri);
|
||||||
var client = new YavscApiClient(settings, new TokenStore(tokensPath));
|
var client = new YavscApiClient(settings, new TokenStore(tokensPath));
|
||||||
|
|
@ -181,7 +182,7 @@ public class YavscApiClientTests
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// YavscApiClient.LoginInteractiveAsync delegates to
|
/// YavscApiClient.LoginInteractiveAsync delegates to
|
||||||
/// Platform.CreateBrowser. We can't override that static cleanly
|
/// Platform.CreateBrowser. We can't override that static cleanly
|
||||||
/// from xunit.v3, so we rebuild the call by re-routing the
|
/// from XUnit.v3, so we rebuild the call by re-routing the
|
||||||
/// Platform.CreateBrowser delegate for the duration of the call.
|
/// Platform.CreateBrowser delegate for the duration of the call.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static async Task LoginWithBrowserAsync(
|
private static async Task LoginWithBrowserAsync(
|
||||||
|
|
@ -214,7 +215,7 @@ public class YavscApiClientTests
|
||||||
File.WriteAllText(tokensPath, JsonSerializer.Serialize(record));
|
File.WriteAllText(tokensPath, JsonSerializer.Serialize(record));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- OidcLoginPhase progress tests ---------------------------------
|
// --- OIDCLoginPhase progress tests ---------------------------------
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Collecting Progress<T> is documented to capture reports
|
/// Collecting Progress<T> is documented to capture reports
|
||||||
|
|
@ -225,7 +226,7 @@ public class YavscApiClientTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task LoginInteractiveAsync_reports_Discovering_then_Success()
|
public async Task LoginInteractiveAsync_reports_Discovering_then_Success()
|
||||||
{
|
{
|
||||||
using var authority = await OidcStubAuthority.StartAsync();
|
using var authority = await OIDCStubAuthority.StartAsync();
|
||||||
using var apiServer = new StubApiServer();
|
using var apiServer = new StubApiServer();
|
||||||
await apiServer.StartAsync();
|
await apiServer.StartAsync();
|
||||||
|
|
||||||
|
|
@ -234,18 +235,18 @@ public class YavscApiClientTests
|
||||||
var client = new YavscApiClient(settings, new TokenStore(tokensPath));
|
var client = new YavscApiClient(settings, new TokenStore(tokensPath));
|
||||||
var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri);
|
var browser = new FakeAuthorizingBrowser(authority.LoopbackRedirectUri);
|
||||||
|
|
||||||
var reported = new System.Collections.Generic.List<OidcLoginPhase>();
|
var reported = new System.Collections.Generic.List<OIDCLoginPhase>();
|
||||||
var progress = new SyncProgress<OidcLoginPhase>(reported);
|
var progress = new SyncProgress<OIDCLoginPhase>(reported);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await LoginWithBrowserAsync(client, browser.CreateBrowser(), progress);
|
await LoginWithBrowserAsync(client, browser.CreateBrowser(), progress);
|
||||||
// SyncProgress captures reports synchronously — no flush needed.
|
// SyncProgress captures reports synchronously — no flush needed.
|
||||||
|
|
||||||
Assert.Contains(OidcLoginPhase.Discovering, reported);
|
Assert.Contains(OIDCLoginPhase.Discovering, reported);
|
||||||
Assert.Contains(OidcLoginPhase.OpeningBrowser, reported);
|
Assert.Contains(OIDCLoginPhase.OpeningBrowser, reported);
|
||||||
Assert.Contains(OidcLoginPhase.ExchangingCode, reported);
|
Assert.Contains(OIDCLoginPhase.ExchangingCode, reported);
|
||||||
Assert.Equal(OidcLoginPhase.Success, Last(reported));
|
Assert.Equal(OIDCLoginPhase.Success, Last(reported));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -256,24 +257,24 @@ public class YavscApiClientTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task LoginInteractiveAsync_reports_Error_when_browser_missing()
|
public async Task LoginInteractiveAsync_reports_Error_when_browser_missing()
|
||||||
{
|
{
|
||||||
using var authority = await OidcStubAuthority.StartAsync();
|
using var authority = await OIDCStubAuthority.StartAsync();
|
||||||
using var apiServer = new StubApiServer();
|
using var apiServer = new StubApiServer();
|
||||||
await apiServer.StartAsync();
|
await apiServer.StartAsync();
|
||||||
|
|
||||||
var settings = BuildSettings(authority, apiServer.BaseUrl);
|
var settings = BuildSettings(authority, apiServer.BaseUrl);
|
||||||
var client = new YavscApiClient(settings, new TokenStore(TokensPath()));
|
var client = new YavscApiClient(settings, new TokenStore(TokensPath()));
|
||||||
var reported = new System.Collections.Generic.List<OidcLoginPhase>();
|
var reported = new System.Collections.Generic.List<OIDCLoginPhase>();
|
||||||
var progress = new SyncProgress<OidcLoginPhase>(reported);
|
var progress = new SyncProgress<OIDCLoginPhase>(reported);
|
||||||
|
|
||||||
var original = Platform.CreateBrowser;
|
var original = Platform.CreateBrowser;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Platform.CreateBrowser = () => null; // simulate no browser wired up
|
Platform.CreateBrowser = () => null; // simulate no browser wired up
|
||||||
await Assert.ThrowsAsync<InvalidOperationException>(
|
await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
() => client.LoginInteractiveAsync(progress));
|
() => client.LoginInteractiveAsync(progress, TestContext.Current.CancellationToken));
|
||||||
// SyncProgress captures reports synchronously — no flush needed.
|
// SyncProgress captures reports synchronously — no flush needed.
|
||||||
|
|
||||||
Assert.Equal(OidcLoginPhase.Error, Last(reported));
|
Assert.Equal(OIDCLoginPhase.Error, Last(reported));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -284,7 +285,7 @@ public class YavscApiClientTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TrySilentLoginAsync_returns_false_when_no_bundle_on_disk()
|
public async Task TrySilentLoginAsync_returns_false_when_no_bundle_on_disk()
|
||||||
{
|
{
|
||||||
using var authority = await OidcStubAuthority.StartAsync();
|
using var authority = await OIDCStubAuthority.StartAsync();
|
||||||
using var apiServer = new StubApiServer();
|
using var apiServer = new StubApiServer();
|
||||||
await apiServer.StartAsync();
|
await apiServer.StartAsync();
|
||||||
|
|
||||||
|
|
@ -293,7 +294,7 @@ public class YavscApiClientTests
|
||||||
// Tokens file deliberately doesn't exist.
|
// Tokens file deliberately doesn't exist.
|
||||||
var client = new YavscApiClient(settings, new TokenStore(tokensPath));
|
var client = new YavscApiClient(settings, new TokenStore(tokensPath));
|
||||||
|
|
||||||
var ok = await client.TrySilentLoginAsync();
|
var ok = await client.TrySilentLoginAsync(null, TestContext.Current.CancellationToken);
|
||||||
Assert.False(ok);
|
Assert.False(ok);
|
||||||
Assert.False(client.HasValidSession);
|
Assert.False(client.HasValidSession);
|
||||||
}
|
}
|
||||||
|
|
@ -301,7 +302,7 @@ public class YavscApiClientTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TrySilentLoginAsync_returns_true_when_access_token_still_valid()
|
public async Task TrySilentLoginAsync_returns_true_when_access_token_still_valid()
|
||||||
{
|
{
|
||||||
using var authority = await OidcStubAuthority.StartAsync();
|
using var authority = await OIDCStubAuthority.StartAsync();
|
||||||
using var apiServer = new StubApiServer();
|
using var apiServer = new StubApiServer();
|
||||||
await apiServer.StartAsync();
|
await apiServer.StartAsync();
|
||||||
|
|
||||||
|
|
@ -314,7 +315,7 @@ public class YavscApiClientTests
|
||||||
{
|
{
|
||||||
await LoginWithBrowserAsync(client, browser.CreateBrowser());
|
await LoginWithBrowserAsync(client, browser.CreateBrowser());
|
||||||
// Login fresh → access token is far from expiry.
|
// Login fresh → access token is far from expiry.
|
||||||
var ok = await client.TrySilentLoginAsync();
|
var ok = await client.TrySilentLoginAsync(null, TestContext.Current.CancellationToken);
|
||||||
Assert.True(ok);
|
Assert.True(ok);
|
||||||
Assert.True(client.HasValidSession);
|
Assert.True(client.HasValidSession);
|
||||||
}
|
}
|
||||||
|
|
@ -327,7 +328,7 @@ public class YavscApiClientTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task TrySilentLoginAsync_returns_true_when_refresh_succeeds()
|
public async Task TrySilentLoginAsync_returns_true_when_refresh_succeeds()
|
||||||
{
|
{
|
||||||
using var authority = await OidcStubAuthority.StartAsync();
|
using var authority = await OIDCStubAuthority.StartAsync();
|
||||||
using var apiServer = new StubApiServer();
|
using var apiServer = new StubApiServer();
|
||||||
await apiServer.StartAsync();
|
await apiServer.StartAsync();
|
||||||
|
|
||||||
|
|
@ -351,13 +352,13 @@ public class YavscApiClientTests
|
||||||
// matches the disk: access expired, refresh still good.
|
// matches the disk: access expired, refresh still good.
|
||||||
var client = new YavscApiClient(settings, store);
|
var client = new YavscApiClient(settings, store);
|
||||||
|
|
||||||
var reported = new System.Collections.Generic.List<OidcLoginPhase>();
|
var reported = new System.Collections.Generic.List<OIDCLoginPhase>();
|
||||||
var progress = new SyncProgress<OidcLoginPhase>(reported);
|
var progress = new SyncProgress<OIDCLoginPhase>(reported);
|
||||||
|
|
||||||
var ok = await client.TrySilentLoginAsync(progress);
|
var ok = await client.TrySilentLoginAsync(progress, TestContext.Current.CancellationToken);
|
||||||
Assert.True(ok, "silent refresh should succeed via the stub authority.");
|
Assert.True(ok, "silent refresh should succeed via the stub authority.");
|
||||||
Assert.Contains(OidcLoginPhase.ExchangingCode, reported);
|
Assert.Contains(OIDCLoginPhase.ExchangingCode, reported);
|
||||||
Assert.Equal(OidcLoginPhase.Success, Last(reported));
|
Assert.Equal(OIDCLoginPhase.Success, Last(reported));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -430,7 +431,7 @@ public class YavscApiClientTests
|
||||||
/// overload stays for tests that don't care about phase events.
|
/// overload stays for tests that don't care about phase events.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static async Task LoginWithBrowserAsync(
|
private static async Task LoginWithBrowserAsync(
|
||||||
YavscApiClient client, IBrowser browser, IProgress<OidcLoginPhase>? progress = null)
|
YavscApiClient client, IBrowser browser, IProgress<OIDCLoginPhase>? progress = null)
|
||||||
{
|
{
|
||||||
var original = Platform.CreateBrowser;
|
var original = Platform.CreateBrowser;
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ namespace PostIt.Services;
|
||||||
/// The set is deliberately small: each value is a milestone an
|
/// The set is deliberately small: each value is a milestone an
|
||||||
/// operator can grep for in logs / StatusMessage, not a heartbeat.
|
/// operator can grep for in logs / StatusMessage, not a heartbeat.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum OidcLoginPhase
|
public enum OIDCLoginPhase
|
||||||
{
|
{
|
||||||
/// <summary>No login in flight (or login has settled).</summary>
|
/// <summary>No login in flight (or login has settled).</summary>
|
||||||
Idle,
|
Idle,
|
||||||
|
|
|
||||||
|
|
@ -91,15 +91,15 @@ public class YavscApiClient : IAsyncDisposable
|
||||||
/// <see cref="LoginPageViewModel.StatusMessage"/> for the human
|
/// <see cref="LoginPageViewModel.StatusMessage"/> for the human
|
||||||
/// text (URLs, error detail).</param>
|
/// text (URLs, error detail).</param>
|
||||||
public async Task LoginInteractiveAsync(
|
public async Task LoginInteractiveAsync(
|
||||||
IProgress<OidcLoginPhase>? progress = null,
|
IProgress<OIDCLoginPhase>? progress = null,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
progress?.Report(OidcLoginPhase.Discovering);
|
progress?.Report(OIDCLoginPhase.Discovering);
|
||||||
|
|
||||||
var browser = Platform.CreateBrowser?.Invoke();
|
var browser = Platform.CreateBrowser?.Invoke();
|
||||||
if (browser is null)
|
if (browser is null)
|
||||||
{
|
{
|
||||||
progress?.Report(OidcLoginPhase.Error);
|
progress?.Report(OIDCLoginPhase.Error);
|
||||||
throw new InvalidOperationException("No browser is available on this platform.");
|
throw new InvalidOperationException("No browser is available on this platform.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,20 +114,20 @@ public class YavscApiClient : IAsyncDisposable
|
||||||
// the moment we ask the browser to open (covers the entire
|
// the moment we ask the browser to open (covers the entire
|
||||||
// user-driven window including the AwaitingCallback wait), and
|
// user-driven window including the AwaitingCallback wait), and
|
||||||
// the moment we trade the code for tokens.
|
// the moment we trade the code for tokens.
|
||||||
progress?.Report(OidcLoginPhase.OpeningBrowser);
|
progress?.Report(OIDCLoginPhase.OpeningBrowser);
|
||||||
var result = await client.LoginAsync(new LoginRequest(), ct).ConfigureAwait(false);
|
var result = await client.LoginAsync(new LoginRequest(), ct).ConfigureAwait(false);
|
||||||
|
|
||||||
if (result.IsError)
|
if (result.IsError)
|
||||||
{
|
{
|
||||||
progress?.Report(OidcLoginPhase.Error);
|
progress?.Report(OIDCLoginPhase.Error);
|
||||||
throw new InvalidOperationException($"OIDC login failed: {result.Error}");
|
throw new InvalidOperationException($"OIDC login failed: {result.Error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
progress?.Report(OidcLoginPhase.ExchangingCode);
|
progress?.Report(OIDCLoginPhase.ExchangingCode);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(result.RefreshToken))
|
if (string.IsNullOrEmpty(result.RefreshToken))
|
||||||
{
|
{
|
||||||
progress?.Report(OidcLoginPhase.Error);
|
progress?.Report(OIDCLoginPhase.Error);
|
||||||
throw new InvalidOperationException(
|
throw new InvalidOperationException(
|
||||||
"Missing refresh_token — vérifie le scope 'offline_access'.");
|
"Missing refresh_token — vérifie le scope 'offline_access'.");
|
||||||
}
|
}
|
||||||
|
|
@ -139,7 +139,7 @@ public class YavscApiClient : IAsyncDisposable
|
||||||
IdToken: result.IdentityToken);
|
IdToken: result.IdentityToken);
|
||||||
|
|
||||||
_store.Save(_tokens);
|
_store.Save(_tokens);
|
||||||
progress?.Report(OidcLoginPhase.Success);
|
progress?.Report(OIDCLoginPhase.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -152,7 +152,7 @@ public class YavscApiClient : IAsyncDisposable
|
||||||
/// phase and returns false so the UI can keep going.
|
/// phase and returns false so the UI can keep going.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<bool> TrySilentLoginAsync(
|
public async Task<bool> TrySilentLoginAsync(
|
||||||
IProgress<OidcLoginPhase>? progress = null,
|
IProgress<OIDCLoginPhase>? progress = null,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
if (!HasValidSession) return false;
|
if (!HasValidSession) return false;
|
||||||
|
|
@ -161,7 +161,7 @@ public class YavscApiClient : IAsyncDisposable
|
||||||
// Access token still has plenty of life — nothing to do.
|
// Access token still has plenty of life — nothing to do.
|
||||||
if (_tokens.AccessTokenExpiresAt - DateTimeOffset.UtcNow > RefreshSkew)
|
if (_tokens.AccessTokenExpiresAt - DateTimeOffset.UtcNow > RefreshSkew)
|
||||||
{
|
{
|
||||||
progress?.Report(OidcLoginPhase.Success);
|
progress?.Report(OIDCLoginPhase.Success);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,19 +172,19 @@ public class YavscApiClient : IAsyncDisposable
|
||||||
// the user back to the login page.
|
// the user back to the login page.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
progress?.Report(OidcLoginPhase.ExchangingCode);
|
progress?.Report(OIDCLoginPhase.ExchangingCode);
|
||||||
await ForceRefreshAsync(ct).ConfigureAwait(false);
|
await ForceRefreshAsync(ct).ConfigureAwait(false);
|
||||||
progress?.Report(OidcLoginPhase.Success);
|
progress?.Report(OIDCLoginPhase.Success);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (RefreshFailedException)
|
catch (RefreshFailedException)
|
||||||
{
|
{
|
||||||
progress?.Report(OidcLoginPhase.Idle);
|
progress?.Report(OIDCLoginPhase.Idle);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
progress?.Report(OidcLoginPhase.Idle);
|
progress?.Report(OIDCLoginPhase.Idle);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,8 @@ public partial class LoginPageViewModel : ViewModelBase
|
||||||
/// callback hand-off: when AwaitingCallback never resolves,
|
/// callback hand-off: when AwaitingCallback never resolves,
|
||||||
/// the OS never re-launched PostIt with the postit:// URL.
|
/// the OS never re-launched PostIt with the postit:// URL.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private OidcLoginPhase _phase = OidcLoginPhase.Idle;
|
private OIDCLoginPhase _phase = OIDCLoginPhase.Idle;
|
||||||
public OidcLoginPhase Phase
|
public OIDCLoginPhase Phase
|
||||||
{
|
{
|
||||||
get => _phase;
|
get => _phase;
|
||||||
private set
|
private set
|
||||||
|
|
@ -122,13 +122,13 @@ public partial class LoginPageViewModel : ViewModelBase
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string PhaseLabel => _phase switch
|
public string PhaseLabel => _phase switch
|
||||||
{
|
{
|
||||||
OidcLoginPhase.Idle => "En attente",
|
OIDCLoginPhase.Idle => "En attente",
|
||||||
OidcLoginPhase.Discovering => "Découverte OIDC…",
|
OIDCLoginPhase.Discovering => "Découverte OIDC…",
|
||||||
OidcLoginPhase.OpeningBrowser => "Ouverture du navigateur…",
|
OIDCLoginPhase.OpeningBrowser => "Ouverture du navigateur…",
|
||||||
OidcLoginPhase.AwaitingCallback => "En attente du callback postit://…",
|
OIDCLoginPhase.AwaitingCallback => "En attente du callback postit://…",
|
||||||
OidcLoginPhase.ExchangingCode => "Échange du code contre les jetons…",
|
OIDCLoginPhase.ExchangingCode => "Échange du code contre les jetons…",
|
||||||
OidcLoginPhase.Success => "Connecté",
|
OIDCLoginPhase.Success => "Connecté",
|
||||||
OidcLoginPhase.Error => "Erreur",
|
OIDCLoginPhase.Error => "Erreur",
|
||||||
_ => _phase.ToString(),
|
_ => _phase.ToString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -275,7 +275,7 @@ public partial class LoginPageViewModel : ViewModelBase
|
||||||
// The progress sink drives Phase / PhaseLabel; StatusMessage
|
// The progress sink drives Phase / PhaseLabel; StatusMessage
|
||||||
// keeps the text detail (URLs, error messages). Same
|
// keeps the text detail (URLs, error messages). Same
|
||||||
// underlying flow, two views.
|
// underlying flow, two views.
|
||||||
var progress = new Progress<OidcLoginPhase>(p => Phase = p);
|
var progress = new Progress<OIDCLoginPhase>(p => Phase = p);
|
||||||
await LoginInteractiveCoreAsync(_api, progress);
|
await LoginInteractiveCoreAsync(_api, progress);
|
||||||
|
|
||||||
IsBusy = false;
|
IsBusy = false;
|
||||||
|
|
@ -299,7 +299,7 @@ public partial class LoginPageViewModel : ViewModelBase
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task LoginInteractiveCoreAsync(
|
private async Task LoginInteractiveCoreAsync(
|
||||||
YavscApiClient api,
|
YavscApiClient api,
|
||||||
IProgress<OidcLoginPhase>? progress = null)
|
IProgress<OIDCLoginPhase>? progress = null)
|
||||||
{
|
{
|
||||||
var original = Platform.CreateBrowser;
|
var original = Platform.CreateBrowser;
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ namespace Yavsc.Controllers
|
||||||
Location = c.Location,
|
Location = c.Location,
|
||||||
EventDate = c.EventDate,
|
EventDate = c.EventDate,
|
||||||
Id = c.Id,
|
Id = c.Id,
|
||||||
Previsional = c.Previsional,
|
Previsional = c.Provisional,
|
||||||
Reason = c.Reason,
|
Reason = c.Reason,
|
||||||
ActivityCode = c.ActivityCode,
|
ActivityCode = c.ActivityCode,
|
||||||
BillingCode = BillingCodes.Rdv
|
BillingCode = BillingCodes.Rdv
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ namespace Yavsc.ApiControllers
|
||||||
{
|
{
|
||||||
|
|
||||||
HairCutQuery query = await _context.HairCutQueries.Include(q => q.Client).
|
HairCutQuery query = await _context.HairCutQueries.Include(q => q.Client).
|
||||||
Include(q => q.Client.PostalAddress).Include(q => q.Prestation).Include(q=>q.Regularisation)
|
Include(q => q.Client.PostalAddress).Include(q => q.Prestation).Include(q=>q.Regularization)
|
||||||
.SingleAsync(q => q.Id == id);
|
.SingleAsync(q => q.Id == id);
|
||||||
if (query.PaymentId!=null)
|
if (query.PaymentId!=null)
|
||||||
return new BadRequestObjectResult(new { error = "An existing payment process already exists" });
|
return new BadRequestObjectResult(new { error = "An existing payment process already exists" });
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ namespace Yavsc
|
||||||
WorkflowHelpers.ConfigureBillingService();
|
WorkflowHelpers.ConfigureBillingService();
|
||||||
|
|
||||||
var firstRegistrar = new Func<ApplicationDbContext, long, IQuery>((db, id) =>
|
var firstRegistrar = new Func<ApplicationDbContext, long, IQuery>((db, id) =>
|
||||||
db.HairCutQueries.Include(q => q.Prestation).Include(q => q.Regularisation).Single(q => q.Id == id));
|
db.HairCutQueries.Include(q => q.Prestation).Include(q => q.Regularization).Single(q => q.Id == id));
|
||||||
|
|
||||||
const string testCode = "Brush";
|
const string testCode = "Brush";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ namespace Yavsc.Controllers
|
||||||
// GET: GeneralSettings
|
// GET: GeneralSettings
|
||||||
public async Task<IActionResult> Index()
|
public async Task<IActionResult> Index()
|
||||||
{
|
{
|
||||||
return View(await _context.GeneralSettings.ToListAsync());
|
return View(await _context.MusicLoverSettings.ToListAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: GeneralSettings/Details/5
|
// GET: GeneralSettings/Details/5
|
||||||
|
|
@ -28,7 +28,7 @@ namespace Yavsc.Controllers
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
MusicLoverSettings generalSettings = await _context.GeneralSettings.SingleAsync(m => m.UserId == id);
|
MusicLoverSettings generalSettings = await _context.MusicLoverSettings.SingleAsync(m => m.UserId == id);
|
||||||
if (generalSettings == null)
|
if (generalSettings == null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
@ -50,7 +50,7 @@ namespace Yavsc.Controllers
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
_context.GeneralSettings.Add(generalSettings);
|
_context.MusicLoverSettings.Add(generalSettings);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +65,7 @@ namespace Yavsc.Controllers
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
MusicLoverSettings generalSettings = await _context.GeneralSettings.SingleAsync(m => m.UserId == id);
|
MusicLoverSettings generalSettings = await _context.MusicLoverSettings.SingleAsync(m => m.UserId == id);
|
||||||
if (generalSettings == null)
|
if (generalSettings == null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
@ -96,7 +96,7 @@ namespace Yavsc.Controllers
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
MusicLoverSettings generalSettings = await _context.GeneralSettings.SingleAsync(m => m.UserId == id);
|
MusicLoverSettings generalSettings = await _context.MusicLoverSettings.SingleAsync(m => m.UserId == id);
|
||||||
if (generalSettings == null)
|
if (generalSettings == null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
@ -110,8 +110,8 @@ namespace Yavsc.Controllers
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
public async Task<IActionResult> DeleteConfirmed(string id)
|
public async Task<IActionResult> DeleteConfirmed(string id)
|
||||||
{
|
{
|
||||||
MusicLoverSettings generalSettings = await _context.GeneralSettings.SingleAsync(m => m.UserId == id);
|
MusicLoverSettings generalSettings = await _context.MusicLoverSettings.SingleAsync(m => m.UserId == id);
|
||||||
_context.GeneralSettings.Remove(generalSettings);
|
_context.MusicLoverSettings.Remove(generalSettings);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ namespace Yavsc.Controllers
|
||||||
.Include(x => x.Prestation)
|
.Include(x => x.Prestation)
|
||||||
.Include(x => x.PerformerProfile.Performer)
|
.Include(x => x.PerformerProfile.Performer)
|
||||||
.Include(x => x.PerformerProfile.Performer.DeviceDeclaration)
|
.Include(x => x.PerformerProfile.Performer.DeviceDeclaration)
|
||||||
.Include(x => x.Regularisation)
|
.Include(x => x.Regularization)
|
||||||
.SingleAsync(m => m.Id == id);
|
.SingleAsync(m => m.Id == id);
|
||||||
query.SelectedProfile = await _context.BrusherProfile.SingleAsync(b => b.UserId == query.PerformerId);
|
query.SelectedProfile = await _context.BrusherProfile.SingleAsync(b => b.UserId == query.PerformerId);
|
||||||
return query;
|
return query;
|
||||||
|
|
@ -82,7 +82,7 @@ namespace Yavsc.Controllers
|
||||||
}
|
}
|
||||||
var paymentInfo = await _context.ConfirmPayment(User.GetUserId(), PayerID, token);
|
var paymentInfo = await _context.ConfirmPayment(User.GetUserId(), PayerID, token);
|
||||||
ViewBag.paymentinfo = paymentInfo;
|
ViewBag.paymentinfo = paymentInfo;
|
||||||
command.Regularisation = paymentInfo.DbContent;
|
command.Regularization = paymentInfo.DbContent;
|
||||||
command.PaymentId = token;
|
command.PaymentId = token;
|
||||||
bool paymentOk = false;
|
bool paymentOk = false;
|
||||||
if (paymentInfo.DetailsFromPayPal != null)
|
if (paymentInfo.DetailsFromPayPal != null)
|
||||||
|
|
@ -174,7 +174,7 @@ namespace Yavsc.Controllers
|
||||||
.Include(x => x.PerformerProfile)
|
.Include(x => x.PerformerProfile)
|
||||||
.Include(x => x.Prestation)
|
.Include(x => x.Prestation)
|
||||||
.Include(x => x.PerformerProfile.Performer)
|
.Include(x => x.PerformerProfile.Performer)
|
||||||
.Include(x => x.Regularisation)
|
.Include(x => x.Regularization)
|
||||||
.SingleOrDefaultAsync(m => m.Id == id);
|
.SingleOrDefaultAsync(m => m.Id == id);
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ namespace Yavsc.Controllers
|
||||||
// GET: Project
|
// GET: Project
|
||||||
public async Task<IActionResult> Index()
|
public async Task<IActionResult> Index()
|
||||||
{
|
{
|
||||||
var applicationDbContext = _context.Project.Include(p => p.Client).Include(p => p.Context).Include(p => p.PerformerProfile).Include(p => p.Regularisation).Include(p => p.Repository);
|
var applicationDbContext = _context.Project.Include(p => p.Client).Include(p => p.Context).Include(p => p.PerformerProfile).Include(p => p.Regularization).Include(p => p.Repository);
|
||||||
return View(await applicationDbContext.ToListAsync());
|
return View(await applicationDbContext.ToListAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@
|
||||||
|
|
||||||
<!-- Yavsc.Org-specific versions -->
|
<!-- Yavsc.Org-specific versions -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="AsciiDocSharp" Version="0.2.0" />
|
<PackageVersion Include="AsciiDocSharp" Version="0.1.0" />
|
||||||
<PackageVersion Include="AsciiDocSharp.Converters.Html" Version="0.2.0" />
|
<PackageVersion Include="AsciiDocSharp.Converters.Html" Version="0.1.0" />
|
||||||
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.6.2" />
|
<PackageVersion Include="BouncyCastle.Cryptography" Version="2.6.2" />
|
||||||
<PackageVersion Include="Google.Apis.Compute.v1" Version="1.74.0.4138" />
|
<PackageVersion Include="Google.Apis.Compute.v1" Version="1.74.0.4138" />
|
||||||
<PackageVersion Include="HigginsSoft.IdentityServer8.AspNetIdentity" Version="8.0.5-preview-net9" />
|
<PackageVersion Include="HigginsSoft.IdentityServer8.AspNetIdentity" Version="8.1.0-alpha.171" />
|
||||||
<PackageVersion Include="HigginsSoft.IdentityServer8.EntityFramework.Storage" Version="8.0.5-preview-net9" />
|
<PackageVersion Include="HigginsSoft.IdentityServer8.EntityFramework.Storage" Version="8.1.0-alpha.171" />
|
||||||
<PackageVersion Include="HigginsSoft.IdentityServer8.Security" Version="8.0.5-preview-net9" />
|
<PackageVersion Include="HigginsSoft.IdentityServer8.Security" Version="8.1.0-alpha.171" />
|
||||||
<PackageVersion Include="HigginsSoft.IdentityServer8.Storage" Version="8.0.5-preview-net9" />
|
<PackageVersion Include="HigginsSoft.IdentityServer8.Storage" Version="8.1.0-alpha.171" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Antiforgery" Version="2.3.11" />
|
<PackageVersion Include="Microsoft.AspNetCore.Antiforgery" Version="2.3.11" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Google" Version="10.0.9" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authentication.Google" Version="10.0.9" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="10.0.9" />
|
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="10.0.9" />
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@ namespace Yavsc.Helpers
|
||||||
{
|
{
|
||||||
Sender = query.ClientId,
|
Sender = query.ClientId,
|
||||||
Reason = query.Reason,
|
Reason = query.Reason,
|
||||||
Client = new ClientProviderInfo {
|
Client = new ClientProviderInfo {
|
||||||
UserName = query.Client.UserName ,
|
UserName = query.Client.UserName ,
|
||||||
UserId = query.ClientId,
|
UserId = query.ClientId,
|
||||||
Avatar = query.Client.Avatar } ,
|
Avatar = query.Client.Avatar } ,
|
||||||
Previsional = query.Previsional,
|
Previsional = query.Provisional,
|
||||||
EventDate = query.EventDate,
|
EventDate = query.EventDate,
|
||||||
Location = query.Location,
|
Location = query.Location,
|
||||||
Id = query.Id,
|
Id = query.Id,
|
||||||
|
|
@ -59,11 +59,11 @@ namespace Yavsc.Helpers
|
||||||
{
|
{
|
||||||
Sender = query.ClientId,
|
Sender = query.ClientId,
|
||||||
|
|
||||||
Client = new ClientProviderInfo {
|
Client = new ClientProviderInfo {
|
||||||
UserName = query.Client.UserName ,
|
UserName = query.Client.UserName ,
|
||||||
UserId = query.ClientId,
|
UserId = query.ClientId,
|
||||||
Avatar = query.Client.Avatar } ,
|
Avatar = query.Client.Avatar } ,
|
||||||
Previsional = query.Previsional,
|
Previsional = query.Provisional,
|
||||||
EventDate = query.EventDate,
|
EventDate = query.EventDate,
|
||||||
Location = query.Location,
|
Location = query.Location,
|
||||||
Id = query.Id,
|
Id = query.Id,
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@
|
||||||
<dd>@await Component.InvokeAsync("Bill", Model)
|
<dd>@await Component.InvokeAsync("Bill", Model)
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt>@Html.DisplayNameFor(m=>m.Regularisation)</dt>
|
<dt>@Html.DisplayNameFor(m=>m.Regularization)</dt>
|
||||||
<dd> @await Component.InvokeAsync("PayPalButton", Model)
|
<dd> @await Component.InvokeAsync("PayPalButton", Model)
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label asp-for="Previsional" class="col-md-2 control-label"></label>
|
<label asp-for="Provisional" class="col-md-2 control-label"></label>
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<input asp-for="Previsional" class="form-control" />
|
<input asp-for="Provisional" class="form-control" />
|
||||||
<span asp-validation-for="Previsional" class="text-danger" ></span>
|
<span asp-validation-for="Provisional" class="text-danger" ></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
||||||
|
|
@ -48,10 +48,10 @@
|
||||||
@Html.DisplayFor(model => model.OwnerId)
|
@Html.DisplayFor(model => model.OwnerId)
|
||||||
</dd>
|
</dd>
|
||||||
<dt>
|
<dt>
|
||||||
@Html.DisplayNameFor(model => model.Previsional)
|
@Html.DisplayNameFor(model => model.Provisional)
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
@Html.DisplayFor(model => model.Previsional)
|
@Html.DisplayFor(model => model.Provisional)
|
||||||
</dd>
|
</dd>
|
||||||
<dt>
|
<dt>
|
||||||
@Html.DisplayNameFor(model => model.DateModified)
|
@Html.DisplayNameFor(model => model.DateModified)
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,10 @@
|
||||||
@Html.DisplayFor(model => model.OwnerId)
|
@Html.DisplayFor(model => model.OwnerId)
|
||||||
</dd>
|
</dd>
|
||||||
<dt>
|
<dt>
|
||||||
@Html.DisplayNameFor(model => model.Previsional)
|
@Html.DisplayNameFor(model => model.Provisional)
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
@Html.DisplayFor(model => model.Previsional)
|
@Html.DisplayFor(model => model.Provisional)
|
||||||
</dd>
|
</dd>
|
||||||
<dt>
|
<dt>
|
||||||
@Html.DisplayNameFor(model => model.DateModified)
|
@Html.DisplayNameFor(model => model.DateModified)
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label asp-for="Previsional" class="col-md-2 control-label">Prévisionel</label>
|
<label asp-for="Provisional" class="col-md-2 control-label">Prévisionel</label>
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<input asp-for="Previsional" class="form-control" />
|
<input asp-for="Provisional" class="form-control" />
|
||||||
<span asp-validation-for="Previsional" class="text-danger" ></span>
|
<span asp-validation-for="Provisional" class="text-danger" ></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
@Html.DisplayNameFor(model => model.OwnerId)
|
@Html.DisplayNameFor(model => model.OwnerId)
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
@Html.DisplayNameFor(model => model.Previsional)
|
@Html.DisplayNameFor(model => model.Provisional)
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
@Html.DisplayNameFor(model => model.DateModified)
|
@Html.DisplayNameFor(model => model.DateModified)
|
||||||
|
|
@ -74,7 +74,7 @@
|
||||||
@Html.DisplayFor(modelItem => item.OwnerId)
|
@Html.DisplayFor(modelItem => item.OwnerId)
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@Html.DisplayFor(modelItem => item.Previsional)
|
@Html.DisplayFor(modelItem => item.Provisional)
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@Html.DisplayFor(modelItem => item.DateModified)
|
@Html.DisplayFor(modelItem => item.DateModified)
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
@if (Model!=null && Model.PaymentId!=null) {
|
@if (Model!=null && Model.PaymentId!=null) {
|
||||||
|
|
||||||
@if (Model.Regularisation.Executor.Id == User.GetUserId()) {
|
@if (Model.Regularization.Executor.Id == User.GetUserId()) {
|
||||||
<text>
|
<text>
|
||||||
Votre paiment
|
Votre paiment
|
||||||
</text>
|
</text>
|
||||||
} else {
|
} else {
|
||||||
<text>
|
<text>
|
||||||
Le paiment de @Html.DisplayFor(m=>m.Regularisation.Executor.UserName)
|
Le paiment de @Html.DisplayFor(m=>m.Regularization.Executor.UserName)
|
||||||
</text>
|
</text>
|
||||||
}
|
}
|
||||||
<text> :
|
<text> :
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
</text>
|
</text>
|
||||||
<label>@Model.GetIsAcquitted()
|
<label>@Model.GetIsAcquitted()
|
||||||
<input type="checkbox" checked="@Model.GetIsAcquitted()" disabled/>
|
<input type="checkbox" checked="@Model.GetIsAcquitted()" disabled/>
|
||||||
<a asp-controller="Manage" asp-action="PaymentInfo" asp-route-id="@Model.Regularisation.CreationToken">@Model.Regularisation.CreationToken</a>
|
<a asp-controller="Manage" asp-action="PaymentInfo" asp-route-id="@Model.Regularization.CreationToken">@Model.Regularization.CreationToken</a>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
</dt>
|
</dt>
|
||||||
<dd>@await Component.InvokeAsync("Bill", Model)
|
<dd>@await Component.InvokeAsync("Bill", Model)
|
||||||
</dd>
|
</dd>
|
||||||
<dt>@Html.DisplayNameFor(m=>m.Regularisation)</dt>
|
<dt>@Html.DisplayNameFor(m=>m.Regularization)</dt>
|
||||||
<dd>
|
<dd>
|
||||||
@await Component.InvokeAsync("PayPalButton", Model)
|
@await Component.InvokeAsync("PayPalButton", Model)
|
||||||
</dd>
|
</dd>
|
||||||
|
|
|
||||||
|
|
@ -76,15 +76,21 @@ namespace Yavsc.Helpers
|
||||||
{
|
{
|
||||||
Config.ProfileTypes.Add(c);
|
Config.ProfileTypes.Add(c);
|
||||||
}
|
}
|
||||||
|
if (c.IsClass && !c.IsAbstract &&
|
||||||
|
c.GetInterface(nameof(IBillable)) != null)
|
||||||
|
{
|
||||||
|
BillingService.GlobalBillingMap.Add(c.Name, c.AssemblyQualifiedName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var propertyInfo in typeof(ApplicationDbContext).GetProperties())
|
foreach (var propertyInfo in typeof(ApplicationDbContext).GetProperties())
|
||||||
{
|
{
|
||||||
foreach (var attr in propertyInfo.CustomAttributes)
|
if (propertyInfo.PropertyType.IsGenericType &&
|
||||||
|
propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>))
|
||||||
{
|
{
|
||||||
// something like a DbSet?
|
var entityType = propertyInfo.PropertyType.GetGenericArguments()[0];
|
||||||
if (typeof(Yavsc.Attributes.ActivitySettingsAttribute).IsAssignableFrom(attr.AttributeType))
|
if (typeof(IUserSettings).IsAssignableFrom(entityType))
|
||||||
{
|
{
|
||||||
BillingService.UserSettings.Add(propertyInfo);
|
BillingService.UserSettings.Add(propertyInfo);
|
||||||
}
|
}
|
||||||
|
|
@ -94,16 +100,16 @@ namespace Yavsc.Helpers
|
||||||
RegisterBilling<HairCutQuery>(BillingCodes.Brush, new Func<ApplicationDbContext, long, IQuery>
|
RegisterBilling<HairCutQuery>(BillingCodes.Brush, new Func<ApplicationDbContext, long, IQuery>
|
||||||
((db, id) =>
|
((db, id) =>
|
||||||
{
|
{
|
||||||
var query = db.HairCutQueries.Include(q => q.Prestation).Include(q => q.Regularisation).Single(q => q.Id == id);
|
var query = db.HairCutQueries.Include(q => q.Prestation).Include(q => q.Regularization).Single(q => q.Id == id);
|
||||||
query.SelectedProfile = db.BrusherProfile.Single(b => b.UserId == query.PerformerId);
|
query.SelectedProfile = db.BrusherProfile.Single(b => b.UserId == query.PerformerId);
|
||||||
return query;
|
return query;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
RegisterBilling<HairMultiCutQuery>(BillingCodes.MBrush, new Func<ApplicationDbContext, long, IQuery>
|
RegisterBilling<HairMultiCutQuery>(BillingCodes.MBrush, new Func<ApplicationDbContext, long, IQuery>
|
||||||
((db, id) => db.HairMultiCutQueries.Include(q => q.Regularisation).Single(q => q.Id == id)));
|
((db, id) => db.HairMultiCutQueries.Include(q => q.Regularization).Single(q => q.Id == id)));
|
||||||
|
|
||||||
RegisterBilling<RdvQuery>(BillingCodes.Rdv, new Func<ApplicationDbContext, long, IQuery>
|
RegisterBilling<RdvQuery>(BillingCodes.Rdv, new Func<ApplicationDbContext, long, IQuery>
|
||||||
((db, id) => db.RdvQueries.Include(q => q.Regularisation).Single(q => q.Id == id)));
|
((db, id) => db.RdvQueries.Include(q => q.Regularization).Single(q => q.Id == id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -290,22 +290,17 @@ namespace Yavsc.Models
|
||||||
|
|
||||||
public DbSet<Instrument> Instrument { get; set; }
|
public DbSet<Instrument> Instrument { get; set; }
|
||||||
|
|
||||||
[ActivitySettings]
|
|
||||||
public DbSet<DjSettings> DjSettings { get; set; }
|
public DbSet<DjSettings> DjSettings { get; set; }
|
||||||
|
|
||||||
[ActivitySettings]
|
|
||||||
public DbSet<Instrumentation> Instrumentation { get; set; }
|
public DbSet<Instrumentation> Instrumentation { get; set; }
|
||||||
|
|
||||||
[ActivitySettings]
|
|
||||||
public DbSet<FormationSettings> FormationSettings { get; set; }
|
public DbSet<FormationSettings> FormationSettings { get; set; }
|
||||||
|
|
||||||
[ActivitySettings]
|
public DbSet<MusicLoverSettings> MusicLoverSettings { get; set; }
|
||||||
public DbSet<MusicLoverSettings> GeneralSettings { get; set; }
|
|
||||||
public DbSet<CoWorking> CoWorking { get; set; }
|
public DbSet<CoWorking> CoWorking { get; set; }
|
||||||
|
|
||||||
private void AddTimestamps(string userId)
|
private void AddTimestamps(string userId)
|
||||||
{
|
{ var entities =
|
||||||
var entities =
|
|
||||||
ChangeTracker.Entries()
|
ChangeTracker.Entries()
|
||||||
.Where(x => x.Entity.GetType().GetInterface(nameof(ITrackedEntity)) != null
|
.Where(x => x.Entity.GetType().GetInterface(nameof(ITrackedEntity)) != null
|
||||||
&& (x.State == EntityState.Added || x.State == EntityState.Modified));
|
&& (x.State == EntityState.Added || x.State == EntityState.Modified));
|
||||||
|
|
@ -360,7 +355,6 @@ namespace Yavsc.Models
|
||||||
public DbSet<DismissClicked> DismissClicked { get; set; }
|
public DbSet<DismissClicked> DismissClicked { get; set; }
|
||||||
|
|
||||||
|
|
||||||
[ActivitySettings]
|
|
||||||
public DbSet<BrusherProfile> BrusherProfile { get; set; }
|
public DbSet<BrusherProfile> BrusherProfile { get; set; }
|
||||||
|
|
||||||
public DbSet<BankIdentity> BankIdentity { get; set; }
|
public DbSet<BankIdentity> BankIdentity { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -59,14 +59,14 @@ namespace Yavsc.Models.Billing
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The performer identifier
|
/// The performer identifier
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ForeignKey("PerformerId"),Display(Name="Préstataire")]
|
[ForeignKey("PerformerId"),Display(Name="PerformerProfile")]
|
||||||
public PerformerProfile PerformerProfile { get; set; }
|
public PerformerProfile PerformerProfile { get; set; }
|
||||||
|
|
||||||
public DateTime? ValidationDate {get; set;}
|
public DateTime? ValidationDate {get; set;}
|
||||||
|
|
||||||
|
|
||||||
[Display(Name="Previsional")]
|
[Display(Name="Provisional")]
|
||||||
public decimal? Previsional { get; set; }
|
public decimal? Provisional { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The bill
|
/// The bill
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -82,7 +82,7 @@ namespace Yavsc.Models.Billing
|
||||||
|
|
||||||
public bool GetIsAcquitted()
|
public bool GetIsAcquitted()
|
||||||
{
|
{
|
||||||
return Regularisation?.IsOk() ?? false;
|
return Regularization?.IsOk() ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetFileBaseName(IBillingService billingService)
|
public string GetFileBaseName(IBillingService billingService)
|
||||||
|
|
@ -93,11 +93,11 @@ namespace Yavsc.Models.Billing
|
||||||
return $"facture-{bcode}-{Id}{ack}";
|
return $"facture-{bcode}-{Id}{ack}";
|
||||||
}
|
}
|
||||||
|
|
||||||
[ForeignKey("Regularisation")]
|
[ForeignKey("Regularization")]
|
||||||
|
|
||||||
public string? PaymentId { get; set; }
|
public string? PaymentId { get; set; }
|
||||||
|
|
||||||
[Display(Name = "Acquittement de la facture")]
|
[Display(Name = "Acquittement de la facture")]
|
||||||
public virtual PayPalPayment Regularisation { get; set; }
|
public virtual PayPalPayment Regularization { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -400,7 +400,7 @@ Prestation.Gender == HairCutGenders.Women ?
|
||||||
UserId = ClientId,
|
UserId = ClientId,
|
||||||
Avatar = Client.Avatar
|
Avatar = Client.Avatar
|
||||||
},
|
},
|
||||||
Previsional = Previsional,
|
Previsional = Provisional,
|
||||||
EventDate = EventDate,
|
EventDate = EventDate,
|
||||||
Location = Location,
|
Location = Location,
|
||||||
Id = Id,
|
Id = Id,
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,6 @@ using Yavsc.Billing;
|
||||||
|
|
||||||
namespace Yavsc.Models.Haircut
|
namespace Yavsc.Models.Haircut
|
||||||
{
|
{
|
||||||
public class HairPrestationCollectionItem {
|
|
||||||
|
|
||||||
[Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
|
||||||
public long Id { get; set; }
|
|
||||||
public long PrestationId { get; set; }
|
|
||||||
[ForeignKeyAttribute("PrestationId")]
|
|
||||||
public virtual HairPrestation Prestation { get; set; }
|
|
||||||
|
|
||||||
public long QueryId { get; set; }
|
|
||||||
|
|
||||||
[ForeignKeyAttribute("QueryId")]
|
|
||||||
public virtual HairMultiCutQuery Query { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class HairMultiCutQuery : NominativeServiceCommand
|
public class HairMultiCutQuery : NominativeServiceCommand
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Yavsc.Models.Haircut
|
||||||
|
{
|
||||||
|
public class HairPrestationCollectionItem {
|
||||||
|
|
||||||
|
[Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public long Id { get; set; }
|
||||||
|
public long PrestationId { get; set; }
|
||||||
|
[ForeignKeyAttribute("PrestationId")]
|
||||||
|
public virtual HairPrestation Prestation { get; set; }
|
||||||
|
|
||||||
|
public long QueryId { get; set; }
|
||||||
|
|
||||||
|
[ForeignKeyAttribute("QueryId")]
|
||||||
|
public virtual HairMultiCutQuery Query { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,9 @@ namespace Yavsc.Services
|
||||||
new Dictionary<string, Func<ApplicationDbContext, long, IQuery>>();
|
new Dictionary<string, Func<ApplicationDbContext, long, IQuery>>();
|
||||||
public static List<PropertyInfo> UserSettings = new List<PropertyInfo>();
|
public static List<PropertyInfo> UserSettings = new List<PropertyInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mapping from activity codes to IUserSettings
|
||||||
|
/// </summary>
|
||||||
public static Dictionary<string, string> GlobalBillingMap =
|
public static Dictionary<string, string> GlobalBillingMap =
|
||||||
new Dictionary<string, string>();
|
new Dictionary<string, string>();
|
||||||
|
|
||||||
|
|
@ -30,7 +33,12 @@ namespace Yavsc.Services
|
||||||
return Task.FromResult(GetBillable(DbContext, billingCode, queryId));
|
return Task.FromResult(GetBillable(DbContext, billingCode, queryId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IQuery GetBillable(ApplicationDbContext context, string billingCode, long queryId) => Billing[billingCode](context, queryId);
|
public static IQuery GetBillable(ApplicationDbContext context, string billingCode, long queryId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<IUserSettings> GetPerformersSettingsAsync(string activityCode, string userId)
|
public async Task<IUserSettings> GetPerformersSettingsAsync(string activityCode, string userId)
|
||||||
{
|
{
|
||||||
var activity = await DbContext.Activities.SingleAsync(a => a.Code == activityCode);
|
var activity = await DbContext.Activities.SingleAsync(a => a.Code == activityCode);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue