- Removed all BTCPay references from services and configuration
- Implemented SilverPAY as sole payment provider (no fallback)
- Fixed JWT authentication with proper key length (256+ bits)
- Added UsersController with full CRUD operations
- Updated User model with Email and Role properties
- Configured TeleBot with real Telegram bot token
- Fixed launchSettings.json with JWT environment variable
- E2E tests passing for authentication, catalog, orders
- Payment creation pending SilverPAY server fix
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
235 lines
7.3 KiB
C#
235 lines
7.3 KiB
C#
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using LittleShop.Data;
|
|
using LittleShop.DTOs;
|
|
using LittleShop.Models;
|
|
using LittleShop.Services;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
|
|
namespace LittleShop.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
[Authorize(Policy = "AdminOnly")]
|
|
public class UsersController : ControllerBase
|
|
{
|
|
private readonly LittleShopContext _context;
|
|
private readonly ILogger<UsersController> _logger;
|
|
|
|
public UsersController(LittleShopContext context, ILogger<UsersController> logger)
|
|
{
|
|
_context = context;
|
|
_logger = logger;
|
|
}
|
|
|
|
[HttpGet]
|
|
public async Task<ActionResult<IEnumerable<UserDto>>> GetUsers()
|
|
{
|
|
try
|
|
{
|
|
var users = await _context.Users
|
|
.Where(u => u.IsActive)
|
|
.Select(u => new UserDto
|
|
{
|
|
Id = u.Id,
|
|
Username = u.Username,
|
|
Email = u.Email,
|
|
Role = u.Role,
|
|
CreatedAt = u.CreatedAt,
|
|
IsActive = u.IsActive
|
|
})
|
|
.ToListAsync();
|
|
|
|
return Ok(users);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error fetching users");
|
|
return StatusCode(500, new { message = "Error fetching users", error = ex.Message });
|
|
}
|
|
}
|
|
|
|
[HttpGet("{id}")]
|
|
public async Task<ActionResult<UserDto>> GetUser(Guid id)
|
|
{
|
|
try
|
|
{
|
|
var user = await _context.Users.FindAsync(id);
|
|
|
|
if (user == null || !user.IsActive)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
return Ok(new UserDto
|
|
{
|
|
Id = user.Id,
|
|
Username = user.Username,
|
|
Email = user.Email,
|
|
Role = user.Role,
|
|
CreatedAt = user.CreatedAt,
|
|
IsActive = user.IsActive
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error fetching user {UserId}", id);
|
|
return StatusCode(500, new { message = "Error fetching user", error = ex.Message });
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<ActionResult<UserDto>> CreateUser([FromBody] CreateUserDto createUserDto)
|
|
{
|
|
try
|
|
{
|
|
// Check if username already exists
|
|
if (await _context.Users.AnyAsync(u => u.Username == createUserDto.Username))
|
|
{
|
|
return BadRequest(new { message = "Username already exists" });
|
|
}
|
|
|
|
// Check if email already exists
|
|
if (!string.IsNullOrEmpty(createUserDto.Email) &&
|
|
await _context.Users.AnyAsync(u => u.Email == createUserDto.Email))
|
|
{
|
|
return BadRequest(new { message = "Email already exists" });
|
|
}
|
|
|
|
var user = new User
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Username = createUserDto.Username,
|
|
Email = createUserDto.Email,
|
|
Role = createUserDto.Role ?? "Staff",
|
|
PasswordHash = HashPassword(createUserDto.Password),
|
|
CreatedAt = DateTime.UtcNow,
|
|
IsActive = true
|
|
};
|
|
|
|
_context.Users.Add(user);
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("User created: {Username}", user.Username);
|
|
|
|
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, new UserDto
|
|
{
|
|
Id = user.Id,
|
|
Username = user.Username,
|
|
Email = user.Email,
|
|
Role = user.Role,
|
|
CreatedAt = user.CreatedAt,
|
|
IsActive = user.IsActive
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error creating user");
|
|
return StatusCode(500, new { message = "Error creating user", error = ex.Message });
|
|
}
|
|
}
|
|
|
|
[HttpPut("{id}")]
|
|
public async Task<IActionResult> UpdateUser(Guid id, [FromBody] UpdateUserDto updateUserDto)
|
|
{
|
|
try
|
|
{
|
|
var user = await _context.Users.FindAsync(id);
|
|
|
|
if (user == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
// Check if username is being changed to an existing one
|
|
if (!string.IsNullOrEmpty(updateUserDto.Username) &&
|
|
updateUserDto.Username != user.Username &&
|
|
await _context.Users.AnyAsync(u => u.Username == updateUserDto.Username))
|
|
{
|
|
return BadRequest(new { message = "Username already exists" });
|
|
}
|
|
|
|
// Check if email is being changed to an existing one
|
|
if (!string.IsNullOrEmpty(updateUserDto.Email) &&
|
|
updateUserDto.Email != user.Email &&
|
|
await _context.Users.AnyAsync(u => u.Email == updateUserDto.Email))
|
|
{
|
|
return BadRequest(new { message = "Email already exists" });
|
|
}
|
|
|
|
// Update fields
|
|
if (!string.IsNullOrEmpty(updateUserDto.Username))
|
|
user.Username = updateUserDto.Username;
|
|
|
|
if (updateUserDto.Email != null)
|
|
user.Email = updateUserDto.Email;
|
|
|
|
if (!string.IsNullOrEmpty(updateUserDto.Role))
|
|
user.Role = updateUserDto.Role;
|
|
|
|
if (!string.IsNullOrEmpty(updateUserDto.Password))
|
|
user.PasswordHash = HashPassword(updateUserDto.Password);
|
|
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("User updated: {UserId}", id);
|
|
|
|
return NoContent();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error updating user {UserId}", id);
|
|
return StatusCode(500, new { message = "Error updating user", error = ex.Message });
|
|
}
|
|
}
|
|
|
|
[HttpDelete("{id}")]
|
|
public async Task<IActionResult> DeleteUser(Guid id)
|
|
{
|
|
try
|
|
{
|
|
var user = await _context.Users.FindAsync(id);
|
|
|
|
if (user == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
// Don't delete the last admin user
|
|
if (user.Role == "Admin")
|
|
{
|
|
var adminCount = await _context.Users.CountAsync(u => u.Role == "Admin" && u.IsActive);
|
|
if (adminCount <= 1)
|
|
{
|
|
return BadRequest(new { message = "Cannot delete the last admin user" });
|
|
}
|
|
}
|
|
|
|
// Soft delete
|
|
user.IsActive = false;
|
|
await _context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation("User deleted: {UserId}", id);
|
|
|
|
return NoContent();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error deleting user {UserId}", id);
|
|
return StatusCode(500, new { message = "Error deleting user", error = ex.Message });
|
|
}
|
|
}
|
|
|
|
private static string HashPassword(string password)
|
|
{
|
|
using var pbkdf2 = new Rfc2898DeriveBytes(
|
|
password,
|
|
salt: Encoding.UTF8.GetBytes("LittleShopSalt2024!"),
|
|
iterations: 100000,
|
|
HashAlgorithmName.SHA256);
|
|
|
|
return Convert.ToBase64String(pbkdf2.GetBytes(32));
|
|
}
|
|
} |