add user service and accounts controller

tests
maxime 1 year ago
parent 2f9975cc2a
commit 7bf6f9c5ec

@ -19,4 +19,8 @@
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Services\Services.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,83 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Model;
using Services;
namespace API.Controllers;
public class AccountsController(UserService service) : ControllerBase
{
private static string _defaultProfilePicture =
"https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png";
[HttpGet("/admin/list-users")]
public async Task<IEnumerable<User>> ListUsers(
[Range(0, int.MaxValue, ErrorMessage = "Only positive number allowed")]
int start,
[Range(0, int.MaxValue, ErrorMessage = "Only positive number allowed")]
int n,
[MaxLength(256, ErrorMessage = "Search string is too wide")]
string? search
)
{
var result = search == null
? await service.ListUsers(search!)
: await service.ListUsers();
return result.Skip(start).Take(n);
}
[HttpGet("/admin/user/{id:int}")]
public async Task<IActionResult> GetUser(
[Range(0, int.MaxValue, ErrorMessage = "Only positive number allowed")]
int id
)
{
var result = await service.GetUser(id);
if (result == null)
return NotFound();
return Ok(result);
}
[HttpPost("/admin/user")]
public Task<User> AddUser(
[MaxLength(256, ErrorMessage = "Username is too wide")]
string username,
[Range(4, 256, ErrorMessage = "Password must length be between 4 and 256")]
string password,
[MaxLength(256, ErrorMessage = "Email is too wide")] [EmailAddress]
string email,
bool isAdmin = false
)
{
return service.CreateUser(username, email, password, _defaultProfilePicture, isAdmin);
}
[HttpDelete("/admin/user")]
public async void RemoveUsers(int[] identifiers)
{
await service.RemoveUsers(identifiers);
}
[HttpPut("/admin/user/{id:int}")]
public async Task<IActionResult> UpdateUser(
int id,
[MaxLength(256, ErrorMessage = "Username is too wide")]
string username,
[MaxLength(256, ErrorMessage = "Email is too wide")] [EmailAddress]
string email,
bool isAdmin
)
{
try
{
await service.UpdateUser(new User(id, username, email, _defaultProfilePicture, isAdmin));
return Ok();
}
catch (ServiceException e)
{
return BadRequest(e.Failures);
}
}
}

@ -1,4 +1,6 @@
using AppContext.Entities;
using System.Security.Cryptography;
using AppContext.Entities;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using Microsoft.EntityFrameworkCore;
namespace AppContext;
@ -17,4 +19,27 @@ public class AppContext(DbContextOptions<AppContext> options) : DbContext(option
)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<UserEntity>()
.Property(e => e.Password)
.HasConversion(
v => HashString(v),
v => v
);
}
private static string HashString(string str)
{
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8);
return Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: str,
salt,
prf: KeyDerivationPrf.HMACSHA256,
iterationCount: 50000,
numBytesRequested: 256 / 8
));
}
}

@ -8,6 +8,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>

@ -4,12 +4,13 @@ namespace AppContext.Entities;
public class UserEntity
{
[Key]
public int Id { get; set; }
[Key] public int Id { get; set; }
public required string Password { get; set; }
public required string Name { get; set; }
public required string Email { get; set; }
public required string ProfilePicture { get; set; }
public required bool IsAdmin { get; set; }
public ICollection<TacticEntity> Tactics { get; set; } = new List<TacticEntity>();
}

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AppContext\AppContext.csproj" />
<ProjectReference Include="..\Model\Model.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,17 @@
using AppContext.Entities;
using Model;
namespace Converters;
public static class EntitiesToModels
{
public static User ToModel(this UserEntity entity)
{
return new User(entity.Id, entity.Name, entity.Email, entity.ProfilePicture, entity.IsAdmin);
}
// public static Team ToModel(this TeamEntity entity)
// {
//
// }
}

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AppContext\AppContext.csproj" />
<ProjectReference Include="..\Converters\Converters.csproj" />
<ProjectReference Include="..\Services\Services.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,72 @@
using AppContext.Entities;
using Converters;
using Microsoft.EntityFrameworkCore;
using Model;
using Services;
using Services.Failures;
namespace DbServices;
public class DbUserService(AppContext.AppContext context) : UserService
{
public Task<IEnumerable<User>> ListUsers(string nameNeedle)
{
return Task.FromResult(
context.Users
.Where(n => n.Name.ToLower().Contains(nameNeedle.ToLower()))
.AsEnumerable()
.Select(e => e.ToModel())
);
}
public Task<IEnumerable<User>> ListUsers()
{
return Task.FromResult(
context.Users
.AsEnumerable()
.Select(e => e.ToModel())
);
}
public async Task<User?> GetUser(int id)
{
return (await context.Users.FirstOrDefaultAsync(e => e.Id == id))?.ToModel();
}
public async Task<User> CreateUser(string username, string email, string password, string profilePicture,
bool isAdmin)
{
var userEntity = new UserEntity
{
Name = username,
Email = email,
Password = password,
ProfilePicture = profilePicture,
IsAdmin = isAdmin
};
await context.Users.AddAsync(userEntity);
await context.SaveChangesAsync();
return userEntity.ToModel();
}
public async Task<bool> RemoveUsers(params int[] identifiers)
{
return await context.Users.Where(u => identifiers.Contains(u.Id)).ExecuteDeleteAsync() > 0;
}
public async Task UpdateUser(User user)
{
var entity = await context.Users.FirstOrDefaultAsync(e => e.Id == user.Id);
if (entity == null)
throw new ServiceException(Failure.NotFound("User not found"));
entity.ProfilePicture = user.ProfilePicture;
entity.Name = user.Name;
entity.Email = user.Email;
entity.Id = user.Id;
await context.SaveChangesAsync();
}
}

@ -0,0 +1,3 @@
namespace Model;
public record User(int Id, string Name, string Email, string ProfilePicture, bool IsAdmin);

@ -0,0 +1,9 @@
namespace Services.Failures;
public record Failure(string Name, string Message)
{
public static Failure NotFound(string message)
{
return new("not found", message);
}
}

@ -0,0 +1,13 @@
using Services.Failures;
namespace Services;
public class ServiceException : Exception
{
public List<Failure> Failures { get; init; }
public ServiceException(params Failure[] failures)
{
Failures = new List<Failure>(failures);
}
}

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Model\Model.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,19 @@
using Model;
namespace Services;
public interface UserService
{
Task<IEnumerable<User>> ListUsers(string nameNeedle);
Task<IEnumerable<User>> ListUsers();
Task<User?> GetUser(int id);
Task<User> CreateUser(string username, string email, string password, string profilePicture, bool isAdmin);
Task<bool> RemoveUsers(params int[] identifiers);
Task UpdateUser(User user);
}

@ -28,6 +28,8 @@ public class StubAppContext(DbContextOptions<AppContext> options) : AppContext(o
Id = ++i,
Email = $"{name}@mail.com",
Name = name,
Password = "123456",
IsAdmin = true,
ProfilePicture =
"https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png",
}));

@ -10,6 +10,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StubContext", "StubContext\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{B22FA426-EFF2-42E9-96BB-78F1C65E37CC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services", "Services\Services.csproj", "{5C342359-9DE6-4B47-9DD9-4F519B58448B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbServices", "DbServices\DbServices.csproj", "{EBBF55CF-97CA-4E7D-8603-5FF546093B95}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Converters", "Converters\Converters.csproj", "{465819A9-7158-4612-AC57-ED2C7A0F243E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -36,5 +42,17 @@ Global
{B22FA426-EFF2-42E9-96BB-78F1C65E37CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B22FA426-EFF2-42E9-96BB-78F1C65E37CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B22FA426-EFF2-42E9-96BB-78F1C65E37CC}.Release|Any CPU.Build.0 = Release|Any CPU
{5C342359-9DE6-4B47-9DD9-4F519B58448B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5C342359-9DE6-4B47-9DD9-4F519B58448B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5C342359-9DE6-4B47-9DD9-4F519B58448B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5C342359-9DE6-4B47-9DD9-4F519B58448B}.Release|Any CPU.Build.0 = Release|Any CPU
{EBBF55CF-97CA-4E7D-8603-5FF546093B95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBBF55CF-97CA-4E7D-8603-5FF546093B95}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBBF55CF-97CA-4E7D-8603-5FF546093B95}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBBF55CF-97CA-4E7D-8603-5FF546093B95}.Release|Any CPU.Build.0 = Release|Any CPU
{465819A9-7158-4612-AC57-ED2C7A0F243E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{465819A9-7158-4612-AC57-ED2C7A0F243E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{465819A9-7158-4612-AC57-ED2C7A0F243E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{465819A9-7158-4612-AC57-ED2C7A0F243E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

Loading…
Cancel
Save