initialize admin panel and necessry pages

This commit is contained in:
2026-09-25 15:01:33 +03:30
parent 598abe604b
commit 9d4ad26dcc
31 changed files with 3565 additions and 7 deletions

View File

@@ -0,0 +1,126 @@
using GreenHome.Api.Filters;
using GreenHome.Application;
using GreenHome.Application.Dtos.Admin;
using GreenHome.Domain;
using Microsoft.AspNetCore.Mvc;
namespace GreenHome.Api.Controllers;
[ApiController]
[Route("api/admin")]
[ServiceFilter(typeof(AdminAuthFilter))]
public sealed class AdminController : ControllerBase
{
private readonly IAdminService _adminService;
public AdminController(IAdminService adminService)
{
_adminService = adminService;
}
// ==================== Dashboard ====================
[HttpGet("dashboard")]
public async Task<ActionResult<AdminDashboardDto>> Dashboard(CancellationToken cancellationToken)
{
var result = await _adminService.GetDashboardAsync(cancellationToken);
return Ok(result);
}
// ==================== Users ====================
[HttpGet("users")]
public async Task<ActionResult<PagedResult<AdminUserListItemDto>>> Users(
[FromQuery] string? search,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
CancellationToken cancellationToken = default)
{
var filter = new AdminUserFilter
{
Search = search,
Page = Math.Max(1, page),
PageSize = Math.Clamp(pageSize, 1, 100)
};
var result = await _adminService.GetUsersAsync(filter, cancellationToken);
return Ok(result);
}
[HttpGet("users/{id:int}")]
public async Task<ActionResult<AdminUserDetailDto>> GetUser(int id, CancellationToken cancellationToken)
{
var result = await _adminService.GetUserAsync(id, cancellationToken);
return result is null ? NotFound() : Ok(result);
}
[HttpPut("users/{id:int}/role")]
public async Task<IActionResult> SetUserRole(int id, [FromBody] ChangeUserRoleRequest request, CancellationToken cancellationToken)
{
var currentAdminId = ((UserDto)HttpContext.Items["AdminUser"]!).Id;
var outcome = await _adminService.SetUserRoleAsync(id, request.Role, currentAdminId, cancellationToken);
return outcome switch
{
UserMutationOutcome.Ok => Ok(new { success = true }),
UserMutationOutcome.NotFound => NotFound(),
UserMutationOutcome.ConflictSelf => Conflict(new { message = "نمی‌توانید نقش خود را تغییر دهید" }),
UserMutationOutcome.ConflictLastAdmin => Conflict(new { message = "آخرین مدیر را نمی‌توان تغییر نقش داد" }),
_ => NotFound()
};
}
[HttpPut("users/{id:int}/status")]
public async Task<IActionResult> SetUserStatus(int id, [FromBody] ChangeUserStatusRequest request, CancellationToken cancellationToken)
{
var currentAdminId = ((UserDto)HttpContext.Items["AdminUser"]!).Id;
var outcome = await _adminService.SetUserStatusAsync(id, request.IsEnabled, currentAdminId, cancellationToken);
return outcome switch
{
UserMutationOutcome.Ok => Ok(new { success = true }),
UserMutationOutcome.NotFound => NotFound(),
UserMutationOutcome.ConflictSelf => Conflict(new { message = "نمی‌توانید اکانت خود را غیرفعال کنید" }),
UserMutationOutcome.ConflictLastAdmin => Conflict(new { message = "آخرین مدیر فعال را نمی‌توان غیرفعال کرد" }),
_ => NotFound()
};
}
// ==================== Devices ====================
[HttpGet("devices")]
public async Task<ActionResult<PagedResult<AdminDeviceListItemDto>>> Devices(
[FromQuery] string? search,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
CancellationToken cancellationToken = default)
{
var filter = new AdminDeviceFilter
{
Search = search,
Page = Math.Max(1, page),
PageSize = Math.Clamp(pageSize, 1, 100)
};
var result = await _adminService.GetDevicesAsync(filter, cancellationToken);
return Ok(result);
}
[HttpGet("devices/{id:int}")]
public async Task<ActionResult<AdminDeviceDetailDto>> GetDevice(int id, CancellationToken cancellationToken)
{
var result = await _adminService.GetDeviceAsync(id, cancellationToken);
return result is null ? NotFound() : Ok(result);
}
// ==================== Usage ====================
[HttpGet("usage")]
public async Task<ActionResult<IReadOnlyList<UsageDayDto>>> Usage(
[FromQuery] DateTime? from,
[FromQuery] DateTime? to,
[FromQuery] int? userId,
CancellationToken cancellationToken = default)
{
var result = await _adminService.GetUsageAsync(from, to, userId, cancellationToken);
return Ok(result);
}
}

View File

@@ -0,0 +1,60 @@
using GreenHome.Application;
using GreenHome.Domain;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace GreenHome.Api.Filters;
/// <summary>
/// Guards all api/admin/* endpoints:
/// - missing/invalid/expired token -> 401
/// - valid token but Role != Admin -> 403
/// Role is always read from the server-side user lookup (IAuthService.GetUserByTokenAsync
/// does a DB lookup), never from client-provided claims.
/// </summary>
public sealed class AdminAuthFilter : IAsyncActionFilter
{
private readonly IAuthService _authService;
public AdminAuthFilter(IAuthService authService)
{
_authService = authService;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var header = context.HttpContext.Request.Headers.Authorization.ToString();
var token = header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)
? header["Bearer ".Length..].Trim()
: null;
if (string.IsNullOrWhiteSpace(token))
{
context.Result = new UnauthorizedObjectResult(new { message = "Token ارسال نشده است" });
return;
}
var user = await _authService.GetUserByTokenAsync(token, context.HttpContext.RequestAborted);
if (user == null)
{
// invalid/expired token OR disabled user
context.Result = new UnauthorizedObjectResult(new { message = "Token نامعتبر است" });
return;
}
if (user.Role != UserRole.Admin)
{
context.Result = new ObjectResult(new { message = "دسترسی شما به این بخش مجاز نیست" })
{
StatusCode = StatusCodes.Status403Forbidden
};
return;
}
// expose the authenticated admin to the controller without a second DB round-trip
context.HttpContext.Items["AdminUser"] = user;
await next();
}
}

View File

@@ -53,6 +53,9 @@ void ConfigureCoreServices(WebApplicationBuilder builder)
builder.Services.AddAutoMapper(typeof(GreenHome.Application.MappingProfile));
builder.Services.AddValidatorsFromAssemblyContaining<GreenHome.Application.DeviceDtoValidator>();
// Admin panel auth filter (used via [ServiceFilter] on AdminController)
builder.Services.AddScoped<GreenHome.Api.Filters.AdminAuthFilter>();
builder.Services.AddInfrastructure(builder.Configuration);
}

View File

@@ -22,5 +22,9 @@
"TokenSecret": "CHANGE_ME_USE_A_STRONG_RANDOM_SECRET_AT_LEAST_32_CHARS"
},
"Admin": {
"OnlineWindowMinutes": 15
},
"AllowedHosts": "*"
}