клиент для tansu
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.10" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.10" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.10" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.10" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.10" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.10" />
|
||||||
|
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.10" />
|
||||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
|
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
|
||||||
<PackageVersion Include="MediatR" Version="14.2.0" />
|
<PackageVersion Include="MediatR" Version="14.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -14,5 +14,12 @@
|
|||||||
},
|
},
|
||||||
"Dev": {
|
"Dev": {
|
||||||
"DefaultUserEmail": "dev@tnsu.kz"
|
"DefaultUserEmail": "dev@tnsu.kz"
|
||||||
|
},
|
||||||
|
"TnsuPortal": {
|
||||||
|
"BaseUrl": "https://api.tnsu.kz",
|
||||||
|
"TenantId": "",
|
||||||
|
"ClientId": "",
|
||||||
|
"ClientSecret": "",
|
||||||
|
"Scope": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
@@ -2,7 +2,9 @@ using Extensions.Hosting.AsyncInitialization;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
using Tnsu.ApprovalRoute.Infrastructure.Persistence;
|
using Tnsu.ApprovalRoute.Infrastructure.Persistence;
|
||||||
|
using Tnsu.ApprovalRoute.Infrastructure.TnsuPortal;
|
||||||
|
|
||||||
namespace Tnsu.ApprovalRoute.Infrastructure;
|
namespace Tnsu.ApprovalRoute.Infrastructure;
|
||||||
|
|
||||||
@@ -16,6 +18,14 @@ public static class InfrastructureServiceCollectionExtensions
|
|||||||
.UseSnakeCaseNamingConvention());
|
.UseSnakeCaseNamingConvention());
|
||||||
services.AddAsyncInitializer<DatabaseMigrationInitializer>();
|
services.AddAsyncInitializer<DatabaseMigrationInitializer>();
|
||||||
|
|
||||||
|
services.Configure<TnsuPortalOptions>(configuration.GetSection("TnsuPortal"));
|
||||||
|
services.AddHttpClient<TnsuPortalTokenProvider>();
|
||||||
|
services.AddHttpClient<HttpTnsuPortalClient>((sp, client) =>
|
||||||
|
{
|
||||||
|
var options = sp.GetRequiredService<IOptions<TnsuPortalOptions>>().Value;
|
||||||
|
client.BaseAddress = new Uri(options.BaseUrl);
|
||||||
|
});
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
namespace Tnsu.ApprovalRoute.Infrastructure.TnsuPortal;
|
||||||
|
|
||||||
|
// Клиент api.tnsu.kz (api-tnsu-portal). GetEmployeeByEmailAsync и GetProjectExecutorsAsync — заглушки:
|
||||||
|
// на момент написания у api-tnsu-portal нет эндпоинта GET /Employee/ByEmail (есть только ByIIN/Manager),
|
||||||
|
// а форма ответа GET /Project/{oid} в его openapi-спеке не типизирована. Заменить на реальные вызовы,
|
||||||
|
// когда эндпоинты будут готовы — токен уже прокидывается через TnsuPortalTokenProvider.
|
||||||
|
public sealed class HttpTnsuPortalClient(HttpClient httpClient, TnsuPortalTokenProvider tokenProvider)
|
||||||
|
{
|
||||||
|
public Task<EmployeeDirectoryInfo> GetEmployeeByEmailAsync(string email, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult(new EmployeeDirectoryInfo(
|
||||||
|
DepartmentId: Guid.Parse("00000000-0000-0000-0000-000000000001"),
|
||||||
|
CompanyId: Guid.Parse("00000000-0000-0000-0000-000000000002")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IReadOnlyList<ProjectExecutorInfo>> GetProjectExecutorsAsync(Guid projectId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult<IReadOnlyList<ProjectExecutorInfo>>([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
using Tnsu.ApprovalRoute.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Tnsu.ApprovalRoute.Infrastructure.TnsuPortal;
|
||||||
|
|
||||||
|
public sealed record EmployeeDirectoryInfo(Guid DepartmentId, Guid CompanyId);
|
||||||
|
|
||||||
|
public sealed record ProjectExecutorInfo(string Email, string Position, ProjectRole ProjectRole);
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace Tnsu.ApprovalRoute.Infrastructure.TnsuPortal;
|
||||||
|
|
||||||
|
public sealed class TnsuPortalOptions
|
||||||
|
{
|
||||||
|
public string BaseUrl { get; set; } = "https://api.tnsu.kz";
|
||||||
|
public string TenantId { get; set; } = string.Empty;
|
||||||
|
public string ClientId { get; set; } = string.Empty;
|
||||||
|
public string ClientSecret { get; set; } = string.Empty;
|
||||||
|
public string Scope { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Tnsu.ApprovalRoute.Infrastructure.TnsuPortal;
|
||||||
|
|
||||||
|
// Сервисный (client-credentials) токен для вызова api.tnsu.kz — не связан с токеном конечного пользователя.
|
||||||
|
public sealed class TnsuPortalTokenProvider(HttpClient httpClient, IOptions<TnsuPortalOptions> options)
|
||||||
|
{
|
||||||
|
private string? _cachedToken;
|
||||||
|
private DateTimeOffset _expiresAt = DateTimeOffset.MinValue;
|
||||||
|
|
||||||
|
public async Task<string> GetAccessTokenAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (_cachedToken is not null && DateTimeOffset.UtcNow < _expiresAt)
|
||||||
|
return _cachedToken;
|
||||||
|
|
||||||
|
var opts = options.Value;
|
||||||
|
var response = await httpClient.PostAsync(
|
||||||
|
$"https://login.microsoftonline.com/{opts.TenantId}/oauth2/v2.0/token",
|
||||||
|
new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["grant_type"] = "client_credentials",
|
||||||
|
["client_id"] = opts.ClientId,
|
||||||
|
["client_secret"] = opts.ClientSecret,
|
||||||
|
["scope"] = opts.Scope
|
||||||
|
}),
|
||||||
|
cancellationToken);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var payload = await response.Content.ReadFromJsonAsync<TokenResponse>(cancellationToken)
|
||||||
|
?? throw new InvalidOperationException("Не удалось получить токен для api.tnsu.kz.");
|
||||||
|
|
||||||
|
_cachedToken = payload.AccessToken;
|
||||||
|
_expiresAt = DateTimeOffset.UtcNow.AddSeconds(payload.ExpiresIn - 60);
|
||||||
|
|
||||||
|
return _cachedToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record TokenResponse(
|
||||||
|
[property: JsonPropertyName("access_token")] string AccessToken,
|
||||||
|
[property: JsonPropertyName("expires_in")] int ExpiresIn);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user