initial commit

This commit is contained in:
2026-07-21 14:35:37 +07:00
commit 0027997ff9
798 changed files with 59083 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
using Indotalent.ConfigFrontEnd.Extensions;
using Indotalent.ConfigFrontEnd.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
namespace Indotalent.ConfigFrontEnd.Common;
public abstract class BaseAppPage : ComponentBase
{
[Inject]
protected AuthenticationStateProvider AuthStateProvider { get; set; } = default!;
[Inject]
protected CurrentUserState State { get; set; } = default!;
protected override async Task OnParametersSetAsync()
{
await SyncUserContext();
await base.OnParametersSetAsync();
}
private async Task SyncUserContext()
{
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var user = authState.User;
if (user.Identity?.IsAuthenticated == true)
{
State.UserId = user.GetUserId();
State.UserName = user.GetUserName();
State.Email = user.GetEmail();
State.FullName = user.GetFullName();
}
}
}
+153
View File
@@ -0,0 +1,153 @@
using Indotalent.ConfigBackEnd.Interfaces;
using Indotalent.Features.Account.Login.Cqrs;
using Indotalent.Infrastructure.Authentication.Identity;
using Indotalent.Shared.Models;
using Microsoft.AspNetCore.Components;
using MudBlazor;
using RestSharp;
using System.Net;
namespace Indotalent.ConfigFrontEnd.Common;
public abstract class BaseService
{
protected readonly IHttpClientFactory ClientFactory;
protected readonly NavigationManager Nav;
protected readonly ISnackbar Snackbar;
protected readonly ICurrentUserService CurrentUserService;
protected readonly TokenProvider TokenProvider;
public BaseService(
IHttpClientFactory clientFactory,
NavigationManager nav,
ISnackbar snackbar,
ICurrentUserService currentUserService,
TokenProvider tokenProvider)
{
ClientFactory = clientFactory;
Nav = nav;
Snackbar = snackbar;
CurrentUserService = currentUserService;
TokenProvider = tokenProvider;
}
protected HttpClient CreateClient()
{
var client = ClientFactory.CreateClient();
if (client.BaseAddress == null)
{
client.BaseAddress = new Uri(Nav.BaseUri);
}
return client;
}
protected async Task<ApiResponse<T>?> ExecuteWithResponseAsync<T>(RestClient client, RestRequest request)
{
try
{
if (!string.IsNullOrEmpty(TokenProvider.Token))
request.AddOrUpdateHeader("Authorization", $"Bearer {TokenProvider.Token}");
if (!string.IsNullOrEmpty(TokenProvider.RefreshToken))
request.AddOrUpdateHeader("X-Refresh-Token", TokenProvider.RefreshToken);
if (!string.IsNullOrEmpty(CurrentUserService.UserId))
{
request.AddOrUpdateHeader("X-UserId", CurrentUserService.UserId);
request.AddOrUpdateHeader("X-UserName", CurrentUserService.UserName ?? string.Empty);
request.AddOrUpdateHeader("X-Email", CurrentUserService.Email ?? string.Empty);
request.AddOrUpdateHeader("X-FullName", CurrentUserService.FullName ?? string.Empty);
}
var response = await client.ExecuteAsync<ApiResponse<T>>(request);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
var httpClient = CreateClient();
var refreshRequestMessage = new HttpRequestMessage(HttpMethod.Post, "/api/account/refresh-token");
if (!string.IsNullOrEmpty(TokenProvider.RefreshToken))
{
refreshRequestMessage.Headers.Add("X-Refresh-Token", TokenProvider.RefreshToken);
}
var refreshResponse = await httpClient.SendAsync(refreshRequestMessage);
if (refreshResponse.IsSuccessStatusCode)
{
var result = await refreshResponse.Content.ReadFromJsonAsync<LoginResponse>();
if (result != null && !string.IsNullOrEmpty(result.Token))
{
TokenProvider.Token = result.Token;
request.AddOrUpdateHeader("Authorization", $"Bearer {TokenProvider.Token}");
response = await client.ExecuteAsync<ApiResponse<T>>(request);
}
}
else
{
Nav.NavigateTo("/account/login");
return default;
}
}
if (response.Data != null)
{
if (!response.Data.IsSuccess)
{
HandleApiError(response.Data);
}
return response.Data;
}
var fallbackMessage = GetFriendlyInfrastructureErrorMessage(response);
Snackbar.Add(fallbackMessage, Severity.Error);
return new ApiResponse<T>
{
IsSuccess = false,
StatusCode = (int)response.StatusCode,
Message = fallbackMessage
};
}
catch (Exception ex)
{
var systemError = $"Client System Error: {ex.Message}";
Snackbar.Add(systemError, Severity.Error);
return new ApiResponse<T> { IsSuccess = false, Message = systemError };
}
}
private void HandleApiError<T>(ApiResponse<T> response)
{
var mainMessage = response.Message ?? "Operation failed";
if (response.Errors != null && response.Errors.Any())
{
var combinedErrors = string.Join(Environment.NewLine, response.Errors.Select(e => $"• {e}"));
Snackbar.Add(combinedErrors, Severity.Error, config =>
{
config.ShowCloseIcon = true;
config.VisibleStateDuration = 3000;
});
}
else
{
Snackbar.Add(mainMessage, Severity.Error);
}
}
private string GetFriendlyInfrastructureErrorMessage(RestResponse response)
{
return response.StatusCode switch
{
HttpStatusCode.ServiceUnavailable => "Server is currently under maintenance.",
HttpStatusCode.GatewayTimeout => "Server took too long to respond.",
HttpStatusCode.Unauthorized => "Session expired. Please login again.",
HttpStatusCode.Forbidden => "You do not have permission to access this resource.",
HttpStatusCode.InternalServerError => "A critical error occurred on the server.",
_ => "Unable to reach the server. Please check your connection."
};
}
}
+37
View File
@@ -0,0 +1,37 @@
using Indotalent.ConfigBackEnd.Interfaces;
using Indotalent.ConfigFrontEnd.Models;
using Indotalent.ConfigFrontEnd.Service;
using MudBlazor;
using MudBlazor.Services;
namespace Indotalent.ConfigFrontEnd;
public static class DI
{
public static IServiceCollection AddConfigFrontEndDI(this IServiceCollection services)
{
services.AddMudServices(config =>
{
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.TopRight;
config.SnackbarConfiguration.PreventDuplicates = true;
config.SnackbarConfiguration.NewestOnTop = true;
config.SnackbarConfiguration.ShowCloseIcon = true;
config.SnackbarConfiguration.VisibleStateDuration = 5000;
config.SnackbarConfiguration.HideTransitionDuration = 300;
config.SnackbarConfiguration.ShowTransitionDuration = 300;
config.SnackbarConfiguration.SnackbarVariant = Variant.Filled;
});
services.AddHttpClient("AppClient", client =>
{
client.Timeout = TimeSpan.FromMinutes(30);
client.DefaultRequestHeaders.Add("Accept", "application/json");
});
services.AddScoped<CurrentUserState>();
services.AddScoped<ICurrentUserService, CurrentUserService>();
return services;
}
}
@@ -0,0 +1,26 @@
using System.Security.Claims;
namespace Indotalent.ConfigFrontEnd.Extensions;
public static class IdentityClaimPrincipalExtension
{
public static string? GetUserId(this ClaimsPrincipal? user)
{
return user?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
public static string? GetEmail(this ClaimsPrincipal? user)
{
return user?.FindFirst(ClaimTypes.Email)?.Value;
}
public static string? GetUserName(this ClaimsPrincipal? user)
{
return user?.FindFirst(ClaimTypes.Name)?.Value;
}
public static string? GetFullName(this ClaimsPrincipal? user)
{
return user?.FindFirst(ClaimTypes.GivenName)?.Value;
}
}
@@ -0,0 +1,44 @@
using Indotalent.ConfigBackEnd.Interfaces;
using Indotalent.ConfigFrontEnd.Extensions;
namespace Indotalent.ConfigFrontEnd.Filter;
public class CurrentUserFilter : IEndpointFilter
{
private readonly ICurrentUserService _currentUserService;
public CurrentUserFilter(ICurrentUserService currentUserService)
{
_currentUserService = currentUserService;
}
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
var httpContext = context.HttpContext;
var user = httpContext.User;
string? userId = user.GetUserId();
string? email = user.GetEmail();
string? userName = user.GetUserName();
string? fullName = user.GetFullName();
if (string.IsNullOrEmpty(userId))
{
userId = httpContext.Request.Headers["X-UserId"].ToString();
userName = httpContext.Request.Headers["X-UserName"].ToString();
email = httpContext.Request.Headers["X-Email"].ToString();
fullName = httpContext.Request.Headers["X-FullName"].ToString();
}
if (!string.IsNullOrEmpty(userId))
{
_currentUserService.UserId = userId;
_currentUserService.UserName = userName;
_currentUserService.Email = email;
_currentUserService.FullName = fullName;
}
return await next(context);
}
}
@@ -0,0 +1,9 @@
namespace Indotalent.ConfigFrontEnd.Models;
public class CurrentUserState
{
public string? UserId { get; set; }
public string? UserName { get; set; }
public string? Email { get; set; }
public string? FullName { get; set; }
}
@@ -0,0 +1,48 @@
using Indotalent.ConfigBackEnd.Interfaces;
using Indotalent.ConfigFrontEnd.Models;
namespace Indotalent.ConfigFrontEnd.Service;
public class CurrentUserService : ICurrentUserService
{
private readonly CurrentUserState _state;
public CurrentUserService(CurrentUserState state)
{
_state = state;
}
public string? UserId
{
get => _state.UserId;
set => _state.UserId = value;
}
public string? UserName
{
get => _state.UserName;
set => _state.UserName = value;
}
public string? Email
{
get => _state.Email;
set => _state.Email = value;
}
public string? FullName
{
get => _state.FullName;
set => _state.FullName = value;
}
public Task<bool> IsInRoleAsync(string role)
{
return Task.FromResult(false);
}
public Task<bool> HasPermissionAsync(string permission)
{
return Task.FromResult(false);
}
}
+35
View File
@@ -0,0 +1,35 @@
using Indotalent.Data.Entities;
using Indotalent.Infrastructure.Authentication.Identity;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace Indotalent.ConfigFrontEnd.Service;
public static class JwtService
{
public static string GenerateNewJwt(ApplicationUser user, JwtSettingsModel _jwtSettings)
{
var authClaims = new List<Claim>
{
new(ClaimTypes.Name, user.UserName ?? ""),
new(ClaimTypes.Email, user.Email ?? ""),
new(ClaimTypes.NameIdentifier, user.Id),
new(ClaimTypes.GivenName, user.FullName ?? ""),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
};
var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Key));
var token = new JwtSecurityToken(
issuer: _jwtSettings.Issuer,
audience: _jwtSettings.Audience,
expires: DateTime.Now.AddMinutes(_jwtSettings.DurationInMinutes),
claims: authClaims,
signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}