Added new API for getting Member (aka Users but for use in FE). User is just used for login/registering.

This commit is contained in:
Joseph Milazzo 2020-12-14 14:33:09 -06:00
parent a920be092d
commit 13ed323949
14 changed files with 172 additions and 65 deletions

View File

@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.1" NoWarn="NU1605" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.1" NoWarn="NU1605" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.1">

View File

@ -1,4 +1,5 @@
using System.Security.Cryptography;
using System;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using API.Data;
@ -15,12 +16,14 @@ namespace API.Controllers
{
private readonly DataContext _context;
private readonly ITokenService _tokenService;
private readonly IUserRepository _userRepository;
private readonly ILogger<AccountController> _logger;
public AccountController(DataContext context, ITokenService tokenService, ILogger<AccountController> logger)
public AccountController(DataContext context, ITokenService tokenService, IUserRepository userRepository, ILogger<AccountController> logger)
{
_context = context;
_tokenService = tokenService;
_userRepository = userRepository;
_logger = logger;
}
@ -39,7 +42,8 @@ namespace API.Controllers
UserName = registerDto.Username.ToLower(),
PasswordHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(registerDto.Password)),
PasswordSalt = hmac.Key,
IsAdmin = registerDto.IsAdmin
IsAdmin = registerDto.IsAdmin,
LastActive = DateTime.Now
};
_context.Users.Add(user);
@ -68,11 +72,17 @@ namespace API.Controllers
{
if (computedHash[i] != user.PasswordHash[i]) return Unauthorized("Invalid password");
}
// Update LastActive on account
user.LastActive = DateTime.Now;
_userRepository.Update(user);
await _userRepository.SaveAllAsync();
return new UserDto()
{
Username = user.UserName,
Token = _tokenService.CreateToken(user)
Token = _tokenService.CreateToken(user),
IsAdmin = user.IsAdmin
};
}

View File

@ -0,0 +1,28 @@
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using API.Data;
using API.DTOs;
using API.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers
{
public class UsersController : BaseApiController
{
private readonly DataContext _context;
private readonly IUserRepository _userRepository;
public UsersController(DataContext context, IUserRepository userRepository)
{
_context = context;
_userRepository = userRepository;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<MemberDto>>> GetUsers()
{
return Ok(await _userRepository.GetMembersAsync());
}
}
}

View File

@ -1,30 +0,0 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace API.Converters
{
/// <summary>
/// Converts a number to a boolean.
/// This is needed for HDHomerun.
/// </summary>
public class JsonBoolNumberConverter : JsonConverter<bool>
{
/// <inheritdoc />
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Number)
{
return Convert.ToBoolean(reader.GetInt32());
}
return reader.GetBoolean();
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
{
writer.WriteBooleanValue(value);
}
}
}

View File

@ -1,26 +0,0 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace API.Converters
{
public class JsonBoolStringConverter : JsonConverter<bool>
{
/// <inheritdoc />
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
return reader.GetString().ToLower() == "true";
}
return reader.GetBoolean();
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
{
writer.WriteBooleanValue(value);
}
}
}

16
API/DTOs/MemberDto.cs Normal file
View File

@ -0,0 +1,16 @@
using System;
namespace API.DTOs
{
/// <summary>
/// Represents a member of a Kavita server.
/// </summary>
public class MemberDto
{
public int Id { get; set; }
public string Username { get; set; }
public DateTime Created { get; set; }
public DateTime LastActive { get; set; }
public bool IsAdmin { get; set; }
}
}

View File

@ -1,6 +1,4 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
using API.Converters;
namespace API.DTOs
{
@ -11,7 +9,6 @@ namespace API.DTOs
[Required]
[StringLength(8, MinimumLength = 4)]
public string Password { get; set; }
[JsonConverter(typeof(JsonBoolNumberConverter))]
public bool IsAdmin { get; set; }
}
}

View File

@ -0,0 +1,61 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using API.DTOs;
using API.Entities;
using API.Interfaces;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
namespace API.Data
{
public class UserRepository : IUserRepository
{
private readonly DataContext _context;
private readonly IMapper _mapper;
public UserRepository(DataContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}
public void Update(AppUser user)
{
_context.Entry(user).State = EntityState.Modified;
}
public async Task<bool> SaveAllAsync()
{
return await _context.SaveChangesAsync() > 0;
}
public async Task<IEnumerable<AppUser>> GetUsersAsync()
{
return await _context.Users.ToListAsync();
}
public async Task<AppUser> GetUserByIdAsync(int id)
{
return await _context.Users.FindAsync(id);
}
public async Task<AppUser> GetUserByUsernameAsync(string username)
{
return await _context.Users.SingleOrDefaultAsync(x => x.UserName == username);
}
public async Task<IEnumerable<MemberDto>> GetMembersAsync()
{
return await _context.Users.ProjectTo<MemberDto>(_mapper.ConfigurationProvider).ToListAsync();
}
public async Task<MemberDto> GetMemberAsync(string username)
{
return await _context.Users.Where(x => x.UserName == username)
.ProjectTo<MemberDto>(_mapper.ConfigurationProvider)
.SingleOrDefaultAsync();
}
}
}

View File

@ -1,6 +1,8 @@
using API.Data;
using API.Helpers;
using API.Interfaces;
using API.Services;
using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -11,6 +13,8 @@ namespace API.Extensions
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services, IConfiguration config)
{
services.AddAutoMapper(typeof(AutoMapperProfiles).Assembly);
services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<ITokenService, TokenService>();
services.AddDbContext<DataContext>(options =>
{

View File

@ -0,0 +1,12 @@
using System.Security.Claims;
namespace API.Extensions
{
public static class ClaimsPrincipalExtensions
{
public static string GetUsername(this ClaimsPrincipal user)
{
return user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
}
}

View File

@ -0,0 +1,14 @@
using API.DTOs;
using API.Entities;
using AutoMapper;
namespace API.Helpers
{
public class AutoMapperProfiles : Profile
{
public AutoMapperProfiles()
{
CreateMap<AppUser, MemberDto>();
}
}
}

View File

@ -0,0 +1,19 @@
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using API.DTOs;
using API.Entities;
namespace API.Interfaces
{
public interface IUserRepository
{
void Update(AppUser user);
Task<bool> SaveAllAsync();
Task<IEnumerable<AppUser>> GetUsersAsync();
Task<AppUser> GetUserByIdAsync(int id);
Task<AppUser> GetUserByUsernameAsync(string username);
Task<IEnumerable<MemberDto>> GetMembersAsync();
Task<MemberDto> GetMemberAsync(string username);
}
}

View File

@ -14,6 +14,7 @@ namespace API.Middleware
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
private readonly IHostEnvironment _env;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger, IHostEnvironment env)
{

View File

@ -11,7 +11,7 @@
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
@ -20,7 +20,7 @@
"API": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
"launchBrowser": false,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {