mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
Update repository, seed and service layers
This commit is contained in:
@@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BusinessLayer.Services
|
||||
{
|
||||
public interface IService<T>
|
||||
where T : class
|
||||
{
|
||||
IEnumerable<T> GetAll(int? limit, int? offset);
|
||||
T? GetById(Guid id);
|
||||
void Add(T entity);
|
||||
void Update(T entity);
|
||||
void Delete(Guid id);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@ using DataAccessLayer.Entities;
|
||||
|
||||
namespace BusinessLayer.Services
|
||||
{
|
||||
public interface IUserService : IService<UserAccount>
|
||||
public interface IUserService
|
||||
{
|
||||
UserAccount? GetByUsername(string username);
|
||||
UserAccount? GetByEmail(string email);
|
||||
Task<IEnumerable<UserAccount>> GetAllAsync(int? limit = null, int? offset = null);
|
||||
Task<UserAccount?> GetByIdAsync(Guid id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,18 @@
|
||||
using DataAccessLayer;
|
||||
using DataAccessLayer.Entities;
|
||||
using DataAccessLayer.Repositories;
|
||||
|
||||
namespace BusinessLayer.Services
|
||||
{
|
||||
public class UserService : IUserService
|
||||
public class UserService(IUserAccountRepository repository) : IUserService
|
||||
{
|
||||
private readonly IUserAccountRepository _userAccountRepository;
|
||||
|
||||
public UserService(IUserAccountRepository userAccountRepository)
|
||||
public async Task<IEnumerable<UserAccount>> GetAllAsync(int? limit = null, int? offset = null)
|
||||
{
|
||||
_userAccountRepository = userAccountRepository;
|
||||
return await repository.GetAll(limit, offset);
|
||||
}
|
||||
|
||||
public IEnumerable<UserAccount> GetAll(int? limit, int? offset)
|
||||
public async Task<UserAccount?> GetByIdAsync(Guid id)
|
||||
{
|
||||
return _userAccountRepository.GetAll(limit, offset);
|
||||
}
|
||||
|
||||
public UserAccount? GetById(Guid id)
|
||||
{
|
||||
return _userAccountRepository.GetById(id);
|
||||
}
|
||||
|
||||
public UserAccount? GetByUsername(string username)
|
||||
{
|
||||
return _userAccountRepository.GetByUsername(username);
|
||||
}
|
||||
|
||||
public UserAccount? GetByEmail(string email)
|
||||
{
|
||||
return _userAccountRepository.GetByEmail(email);
|
||||
}
|
||||
|
||||
public void Add(UserAccount userAccount)
|
||||
{
|
||||
_userAccountRepository.Add(userAccount);
|
||||
}
|
||||
|
||||
public void Update(UserAccount userAccount)
|
||||
{
|
||||
_userAccountRepository.Update(userAccount);
|
||||
}
|
||||
|
||||
public void Delete(Guid id)
|
||||
{
|
||||
_userAccountRepository.Delete(id);
|
||||
return await repository.GetById(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ namespace DALTests
|
||||
{
|
||||
public class UserAccountRepositoryTests
|
||||
{
|
||||
private readonly IUserAccountRepository _repository = new UserAccountRepository();
|
||||
private readonly IUserAccountRepository _repository = new InMemoryUserAccountRepository();
|
||||
|
||||
[Fact]
|
||||
public void Add_ShouldInsertUserAccount()
|
||||
public async Task Add_ShouldInsertUserAccount()
|
||||
{
|
||||
// Arrange
|
||||
var userAccount = new UserAccount
|
||||
@@ -24,8 +24,8 @@ namespace DALTests
|
||||
};
|
||||
|
||||
// Act
|
||||
_repository.Add(userAccount);
|
||||
var retrievedUser = _repository.GetById(userAccount.UserAccountId);
|
||||
await _repository.Add(userAccount);
|
||||
var retrievedUser = await _repository.GetById(userAccount.UserAccountId);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(retrievedUser);
|
||||
@@ -33,7 +33,7 @@ namespace DALTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetById_ShouldReturnUserAccount()
|
||||
public async Task GetById_ShouldReturnUserAccount()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
@@ -47,10 +47,10 @@ namespace DALTests
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
DateOfBirth = new DateTime(1985, 5, 15),
|
||||
};
|
||||
_repository.Add(userAccount);
|
||||
await _repository.Add(userAccount);
|
||||
|
||||
// Act
|
||||
var retrievedUser = _repository.GetById(userId);
|
||||
var retrievedUser = await _repository.GetById(userId);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(retrievedUser);
|
||||
@@ -58,7 +58,7 @@ namespace DALTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Update_ShouldModifyUserAccount()
|
||||
public async Task Update_ShouldModifyUserAccount()
|
||||
{
|
||||
// Arrange
|
||||
var userAccount = new UserAccount
|
||||
@@ -71,12 +71,12 @@ namespace DALTests
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
DateOfBirth = new DateTime(1992, 3, 10),
|
||||
};
|
||||
_repository.Add(userAccount);
|
||||
await _repository.Add(userAccount);
|
||||
|
||||
// Act
|
||||
userAccount.FirstName = "Updated";
|
||||
_repository.Update(userAccount);
|
||||
var updatedUser = _repository.GetById(userAccount.UserAccountId);
|
||||
await _repository.Update(userAccount);
|
||||
var updatedUser = await _repository.GetById(userAccount.UserAccountId);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(updatedUser);
|
||||
@@ -84,7 +84,7 @@ namespace DALTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Delete_ShouldRemoveUserAccount()
|
||||
public async Task Delete_ShouldRemoveUserAccount()
|
||||
{
|
||||
// Arrange
|
||||
var userAccount = new UserAccount
|
||||
@@ -97,18 +97,18 @@ namespace DALTests
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
DateOfBirth = new DateTime(1995, 7, 20),
|
||||
};
|
||||
_repository.Add(userAccount);
|
||||
await _repository.Add(userAccount);
|
||||
|
||||
// Act
|
||||
_repository.Delete(userAccount.UserAccountId);
|
||||
var deletedUser = _repository.GetById(userAccount.UserAccountId);
|
||||
await _repository.Delete(userAccount.UserAccountId);
|
||||
var deletedUser = await _repository.GetById(userAccount.UserAccountId);
|
||||
|
||||
// Assert
|
||||
Assert.Null(deletedUser);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAll_ShouldReturnAllUserAccounts()
|
||||
public async Task GetAll_ShouldReturnAllUserAccounts()
|
||||
{
|
||||
// Arrange
|
||||
var user1 = new UserAccount
|
||||
@@ -131,11 +131,11 @@ namespace DALTests
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
DateOfBirth = new DateTime(1992, 2, 2),
|
||||
};
|
||||
_repository.Add(user1);
|
||||
_repository.Add(user2);
|
||||
await _repository.Add(user1);
|
||||
await _repository.Add(user2);
|
||||
|
||||
// Act
|
||||
var allUsers = _repository.GetAll(null, null);
|
||||
var allUsers = await _repository.GetAll(null, null);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(allUsers);
|
||||
@@ -143,7 +143,7 @@ namespace DALTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAll_WithPagination_ShouldRespectLimit()
|
||||
public async Task GetAll_WithPagination_ShouldRespectLimit()
|
||||
{
|
||||
// Arrange
|
||||
var users = new List<UserAccount>
|
||||
@@ -182,25 +182,99 @@ namespace DALTests
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
_repository.Add(user);
|
||||
await _repository.Add(user);
|
||||
}
|
||||
|
||||
// Act
|
||||
var page = _repository.GetAll(2, 0).ToList();
|
||||
var page = (await _repository.GetAll(2, 0)).ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, page.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAll_WithPagination_ShouldValidateArguments()
|
||||
public async Task GetAll_WithPagination_ShouldValidateArguments()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
_repository.GetAll(0, 0).ToList()
|
||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
|
||||
(await _repository.GetAll(0, 0)).ToList()
|
||||
);
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
_repository.GetAll(1, -1).ToList()
|
||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
|
||||
(await _repository.GetAll(1, -1)).ToList()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
internal class InMemoryUserAccountRepository : IUserAccountRepository
|
||||
{
|
||||
private readonly Dictionary<Guid, UserAccount> _store = new();
|
||||
|
||||
public Task Add(UserAccount userAccount)
|
||||
{
|
||||
if (userAccount.UserAccountId == Guid.Empty)
|
||||
{
|
||||
userAccount.UserAccountId = Guid.NewGuid();
|
||||
}
|
||||
_store[userAccount.UserAccountId] = Clone(userAccount);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<UserAccount?> GetById(Guid id)
|
||||
{
|
||||
_store.TryGetValue(id, out var user);
|
||||
return Task.FromResult(user is null ? null : Clone(user));
|
||||
}
|
||||
|
||||
public Task<IEnumerable<UserAccount>> GetAll(int? limit, int? offset)
|
||||
{
|
||||
if (limit.HasValue && limit.Value <= 0) throw new ArgumentOutOfRangeException(nameof(limit));
|
||||
if (offset.HasValue && offset.Value < 0) throw new ArgumentOutOfRangeException(nameof(offset));
|
||||
|
||||
var query = _store.Values
|
||||
.OrderBy(u => u.Username)
|
||||
.Select(Clone);
|
||||
|
||||
if (offset.HasValue) query = query.Skip(offset.Value);
|
||||
if (limit.HasValue) query = query.Take(limit.Value);
|
||||
|
||||
return Task.FromResult<IEnumerable<UserAccount>>(query.ToList());
|
||||
}
|
||||
|
||||
public Task Update(UserAccount userAccount)
|
||||
{
|
||||
if (!_store.ContainsKey(userAccount.UserAccountId)) return Task.CompletedTask;
|
||||
_store[userAccount.UserAccountId] = Clone(userAccount);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task Delete(Guid id)
|
||||
{
|
||||
_store.Remove(id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<UserAccount?> GetByUsername(string username)
|
||||
{
|
||||
var user = _store.Values.FirstOrDefault(u => u.Username == username);
|
||||
return Task.FromResult(user is null ? null : Clone(user));
|
||||
}
|
||||
|
||||
public Task<UserAccount?> GetByEmail(string email)
|
||||
{
|
||||
var user = _store.Values.FirstOrDefault(u => u.Email == email);
|
||||
return Task.FromResult(user is null ? null : Clone(user));
|
||||
}
|
||||
|
||||
private static UserAccount Clone(UserAccount u) => new()
|
||||
{
|
||||
UserAccountId = u.UserAccountId,
|
||||
Username = u.Username,
|
||||
FirstName = u.FirstName,
|
||||
LastName = u.LastName,
|
||||
Email = u.Email,
|
||||
CreatedAt = u.CreatedAt,
|
||||
UpdatedAt = u.UpdatedAt,
|
||||
DateOfBirth = u.DateOfBirth,
|
||||
Timer = u.Timer is null ? null : (byte[])u.Timer.Clone(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace DBSeed
|
||||
|
||||
internal class UserSeeder : ISeeder
|
||||
{
|
||||
private UserAccountRepository _userAccountRepository = new UserAccountRepository();
|
||||
|
||||
|
||||
private static readonly IReadOnlyList<(
|
||||
@@ -133,15 +132,17 @@ namespace DBSeed
|
||||
foreach (var (firstName, lastName) in SeedNames)
|
||||
{
|
||||
// create the user in the database
|
||||
var ua = new UserAccount
|
||||
var userAccountId = Guid.NewGuid();
|
||||
await AddUserAccountAsync(connection, new UserAccount
|
||||
{
|
||||
UserAccountId = userAccountId,
|
||||
FirstName = firstName,
|
||||
LastName = lastName,
|
||||
Email = $"{firstName}.{lastName}@thebiergarten.app",
|
||||
Username = $"{firstName[0]}.{lastName}",
|
||||
DateOfBirth = GenerateDateOfBirth(rng)
|
||||
};
|
||||
|
||||
});
|
||||
createdUsers++;
|
||||
|
||||
// add user credentials
|
||||
if (!await HasUserCredentialAsync(connection, userAccountId))
|
||||
@@ -167,6 +168,21 @@ namespace DBSeed
|
||||
Console.WriteLine($"Added {createdVerifications} user verifications.");
|
||||
}
|
||||
|
||||
private static async Task AddUserAccountAsync(SqlConnection connection, UserAccount ua)
|
||||
{
|
||||
await using var command = new SqlCommand("usp_CreateUserAccount", connection);
|
||||
command.CommandType = CommandType.StoredProcedure;
|
||||
|
||||
command.Parameters.Add("@UserAccountId", SqlDbType.UniqueIdentifier).Value = ua.UserAccountId;
|
||||
command.Parameters.Add("@Username", SqlDbType.NVarChar, 100).Value = ua.Username;
|
||||
command.Parameters.Add("@FirstName", SqlDbType.NVarChar, 100).Value = ua.FirstName;
|
||||
command.Parameters.Add("@LastName", SqlDbType.NVarChar, 100).Value = ua.LastName;
|
||||
command.Parameters.Add("@Email", SqlDbType.NVarChar, 256).Value = ua.Email;
|
||||
command.Parameters.Add("@DateOfBirth", SqlDbType.Date).Value = ua.DateOfBirth;
|
||||
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
private static string GeneratePasswordHash(string pwd)
|
||||
{
|
||||
byte[] salt = RandomNumberGenerator.GetBytes(16);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace DataAccessLayer.Repositories
|
||||
{
|
||||
public interface IRepository<T>
|
||||
where T : class
|
||||
{
|
||||
void Add(T entity);
|
||||
|
||||
IEnumerable<T> GetAll(int? limit, int? offset);
|
||||
|
||||
T? GetById(Guid id);
|
||||
void Update(T entity);
|
||||
void Delete(Guid id);
|
||||
|
||||
T MapToEntity(SqlDataReader entity);
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,14 @@ using DataAccessLayer.Entities;
|
||||
|
||||
namespace DataAccessLayer.Repositories
|
||||
{
|
||||
public interface IUserAccountRepository : IRepository<UserAccount>
|
||||
public interface IUserAccountRepository
|
||||
{
|
||||
UserAccount? GetByUsername(string username);
|
||||
UserAccount? GetByEmail(string email);
|
||||
Task Add(UserAccount userAccount);
|
||||
Task<UserAccount?> GetById(Guid id);
|
||||
Task<IEnumerable<UserAccount>> GetAll(int? limit, int? offset);
|
||||
Task Update(UserAccount userAccount);
|
||||
Task Delete(Guid id);
|
||||
Task<UserAccount?> GetByUsername(string username);
|
||||
Task<UserAccount?> GetByEmail(string email);
|
||||
}
|
||||
}
|
||||
|
||||
24
DataAccessLayer/Repositories/Repository.cs
Normal file
24
DataAccessLayer/Repositories/Repository.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using DataAccessLayer.Sql;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace DataAccessLayer.Repositories
|
||||
{
|
||||
public abstract class Repository<T>(ISqlConnectionFactory connectionFactory)
|
||||
where T : class
|
||||
{
|
||||
protected async Task<SqlConnection> CreateConnection()
|
||||
{
|
||||
var connection = connectionFactory.CreateConnection();
|
||||
await connection.OpenAsync();
|
||||
return connection;
|
||||
}
|
||||
|
||||
public abstract Task Add(T entity);
|
||||
public abstract Task<IEnumerable<T>> GetAll(int? limit, int? offset);
|
||||
public abstract Task<T?> GetById(Guid id);
|
||||
public abstract Task Update(T entity);
|
||||
public abstract Task Delete(Guid id);
|
||||
|
||||
protected abstract T MapToEntity(SqlDataReader reader);
|
||||
}
|
||||
}
|
||||
@@ -1,139 +1,59 @@
|
||||
using DataAccessLayer.Entities;
|
||||
using DataAccessLayer.Sql;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using System.Data;
|
||||
|
||||
namespace DataAccessLayer.Repositories
|
||||
{
|
||||
public class UserAccountRepository : IUserAccountRepository
|
||||
public class UserAccountRepository(ISqlConnectionFactory connectionFactory)
|
||||
: Repository<UserAccount>(connectionFactory), IUserAccountRepository
|
||||
{
|
||||
private readonly string _connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING")
|
||||
?? throw new InvalidOperationException(
|
||||
"The connection string is not set in the environment variables."
|
||||
);
|
||||
|
||||
public void Add(UserAccount userAccount)
|
||||
public override async Task Add(UserAccount userAccount)
|
||||
{
|
||||
using SqlConnection connection = new(_connectionString);
|
||||
using SqlCommand command = new("usp_CreateUserAccount", connection);
|
||||
command.CommandType = System.Data.CommandType.StoredProcedure;
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = new SqlCommand("usp_CreateUserAccount", connection);
|
||||
command.CommandType = CommandType.StoredProcedure;
|
||||
|
||||
command.Parameters.AddWithValue(
|
||||
"@UserAccountId",
|
||||
userAccount.UserAccountId
|
||||
);
|
||||
command.Parameters.AddWithValue("@Username", userAccount.Username);
|
||||
command.Parameters.AddWithValue(
|
||||
"@FirstName",
|
||||
userAccount.FirstName
|
||||
);
|
||||
command.Parameters.AddWithValue("@LastName", userAccount.LastName);
|
||||
command.Parameters.AddWithValue("@Email", userAccount.Email);
|
||||
command.Parameters.AddWithValue(
|
||||
"@DateOfBirth",
|
||||
userAccount.DateOfBirth
|
||||
);
|
||||
connection.Open();
|
||||
command.ExecuteNonQuery();
|
||||
command.Parameters.Add("@UserAccountId", SqlDbType.UniqueIdentifier).Value = userAccount.UserAccountId;
|
||||
command.Parameters.Add("@Username", SqlDbType.NVarChar, 100).Value = userAccount.Username;
|
||||
command.Parameters.Add("@FirstName", SqlDbType.NVarChar, 100).Value = userAccount.FirstName;
|
||||
command.Parameters.Add("@LastName", SqlDbType.NVarChar, 100).Value = userAccount.LastName;
|
||||
command.Parameters.Add("@Email", SqlDbType.NVarChar, 256).Value = userAccount.Email;
|
||||
command.Parameters.Add("@DateOfBirth", SqlDbType.Date).Value = userAccount.DateOfBirth;
|
||||
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
public UserAccount? GetById(Guid id)
|
||||
public override async Task<UserAccount?> GetById(Guid id)
|
||||
{
|
||||
using SqlConnection connection = new(_connectionString);
|
||||
using SqlCommand command = new(
|
||||
"usp_GetUserAccountById",
|
||||
connection
|
||||
);
|
||||
command.CommandType = System.Data.CommandType.StoredProcedure;
|
||||
command.Parameters.AddWithValue("@UserAccountId", id);
|
||||
connection.Open();
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = new SqlCommand("usp_GetUserAccountById", connection)
|
||||
{
|
||||
CommandType = CommandType.StoredProcedure
|
||||
};
|
||||
|
||||
using SqlDataReader reader = command.ExecuteReader();
|
||||
return reader.Read() ? MapToEntity(reader) : null;
|
||||
command.Parameters.Add("@UserAccountId", SqlDbType.UniqueIdentifier).Value = id;
|
||||
|
||||
await using var reader = await command.ExecuteReaderAsync();
|
||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||
}
|
||||
|
||||
public void Update(UserAccount userAccount)
|
||||
public override async Task<IEnumerable<UserAccount>> GetAll(int? limit, int? offset)
|
||||
{
|
||||
using SqlConnection connection = new(_connectionString);
|
||||
using SqlCommand command = new("usp_UpdateUserAccount", connection);
|
||||
command.CommandType = System.Data.CommandType.StoredProcedure;
|
||||
command.Parameters.AddWithValue(
|
||||
"@UserAccountId",
|
||||
userAccount.UserAccountId
|
||||
);
|
||||
command.Parameters.AddWithValue("@Username", userAccount.Username);
|
||||
command.Parameters.AddWithValue(
|
||||
"@FirstName",
|
||||
userAccount.FirstName
|
||||
);
|
||||
command.Parameters.AddWithValue("@LastName", userAccount.LastName);
|
||||
command.Parameters.AddWithValue("@Email", userAccount.Email);
|
||||
command.Parameters.AddWithValue(
|
||||
"@DateOfBirth",
|
||||
userAccount.DateOfBirth
|
||||
);
|
||||
command.Parameters.AddWithValue(
|
||||
"@UserAccountId",
|
||||
userAccount.UserAccountId
|
||||
);
|
||||
connection.Open();
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = new SqlCommand("usp_GetAllUserAccounts", connection);
|
||||
command.CommandType = CommandType.StoredProcedure;
|
||||
|
||||
public void Delete(Guid id)
|
||||
{
|
||||
using SqlConnection connection = new(_connectionString);
|
||||
using SqlCommand command = new("usp_DeleteUserAccount", connection);
|
||||
command.CommandType = System.Data.CommandType.StoredProcedure;
|
||||
command.Parameters.AddWithValue("@UserAccountId", id);
|
||||
connection.Open();
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<UserAccount> GetAll(int? limit, int? offset)
|
||||
{
|
||||
if (limit is <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(limit),
|
||||
"Limit must be greater than zero."
|
||||
);
|
||||
}
|
||||
|
||||
if (offset < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(offset),
|
||||
"Offset cannot be negative."
|
||||
);
|
||||
}
|
||||
|
||||
if (offset.HasValue && !limit.HasValue)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(offset),
|
||||
"Offset cannot be provided without a limit."
|
||||
);
|
||||
}
|
||||
|
||||
using SqlConnection connection = new(_connectionString);
|
||||
using SqlCommand command = new(
|
||||
"usp_GetAllUserAccounts",
|
||||
connection
|
||||
);
|
||||
command.CommandType = System.Data.CommandType.StoredProcedure;
|
||||
if (limit.HasValue)
|
||||
{
|
||||
command.Parameters.AddWithValue("@Limit", limit.Value);
|
||||
}
|
||||
if (offset.HasValue)
|
||||
{
|
||||
command.Parameters.AddWithValue("@Offset", offset.Value);
|
||||
}
|
||||
connection.Open();
|
||||
command.Parameters.Add("@Limit", SqlDbType.Int).Value = limit.Value;
|
||||
|
||||
using SqlDataReader reader = command.ExecuteReader();
|
||||
List<UserAccount> users = new();
|
||||
while (reader.Read())
|
||||
if (offset.HasValue)
|
||||
command.Parameters.Add("@Offset", SqlDbType.Int).Value = offset.Value;
|
||||
|
||||
await using var reader = await command.ExecuteReaderAsync();
|
||||
var users = new List<UserAccount>();
|
||||
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
users.Add(MapToEntity(reader));
|
||||
}
|
||||
@@ -141,49 +61,73 @@ namespace DataAccessLayer.Repositories
|
||||
return users;
|
||||
}
|
||||
|
||||
public UserAccount? GetByUsername(string username)
|
||||
public override async Task Update(UserAccount userAccount)
|
||||
{
|
||||
using SqlConnection connection = new(_connectionString);
|
||||
using SqlCommand command = new(
|
||||
"usp_GetUserAccountByUsername",
|
||||
connection
|
||||
);
|
||||
command.CommandType = System.Data.CommandType.StoredProcedure;
|
||||
command.Parameters.AddWithValue("@Username", username);
|
||||
connection.Open();
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = new SqlCommand("usp_UpdateUserAccount", connection);
|
||||
command.CommandType = CommandType.StoredProcedure;
|
||||
|
||||
using SqlDataReader? reader = command.ExecuteReader();
|
||||
return reader.Read() ? MapToEntity(reader) : null;
|
||||
command.Parameters.Add("@UserAccountId", SqlDbType.UniqueIdentifier).Value = userAccount.UserAccountId;
|
||||
command.Parameters.Add("@Username", SqlDbType.NVarChar, 100).Value = userAccount.Username;
|
||||
command.Parameters.Add("@FirstName", SqlDbType.NVarChar, 100).Value = userAccount.FirstName;
|
||||
command.Parameters.Add("@LastName", SqlDbType.NVarChar, 100).Value = userAccount.LastName;
|
||||
command.Parameters.Add("@Email", SqlDbType.NVarChar, 256).Value = userAccount.Email;
|
||||
command.Parameters.Add("@DateOfBirth", SqlDbType.Date).Value = userAccount.DateOfBirth;
|
||||
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
public UserAccount? GetByEmail(string email)
|
||||
public override async Task Delete(Guid id)
|
||||
{
|
||||
using SqlConnection connection = new(_connectionString);
|
||||
using SqlCommand command = new(
|
||||
"usp_GetUserAccountByEmail",
|
||||
connection
|
||||
);
|
||||
command.CommandType = System.Data.CommandType.StoredProcedure;
|
||||
command.Parameters.AddWithValue("@Email", email);
|
||||
connection.Open();
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = new SqlCommand("usp_DeleteUserAccount", connection);
|
||||
command.CommandType = CommandType.StoredProcedure;
|
||||
|
||||
using SqlDataReader reader = command.ExecuteReader();
|
||||
return reader.Read() ? MapToEntity(reader) : null;
|
||||
command.Parameters.Add("@UserAccountId", SqlDbType.UniqueIdentifier).Value = id;
|
||||
await command.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
public UserAccount MapToEntity(SqlDataReader reader)
|
||||
public async Task<UserAccount?> GetByUsername(string username)
|
||||
{
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = new SqlCommand("usp_GetUserAccountByUsername", connection);
|
||||
command.CommandType = CommandType.StoredProcedure;
|
||||
|
||||
command.Parameters.Add("@Username", SqlDbType.NVarChar, 100).Value = username;
|
||||
|
||||
await using var reader = await command.ExecuteReaderAsync();
|
||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||
}
|
||||
|
||||
public async Task<UserAccount?> GetByEmail(string email)
|
||||
{
|
||||
await using var connection = await CreateConnection();
|
||||
await using var command = new SqlCommand("usp_GetUserAccountByEmail", connection);
|
||||
command.CommandType = CommandType.StoredProcedure;
|
||||
|
||||
command.Parameters.Add("@Email", SqlDbType.NVarChar, 256).Value = email;
|
||||
|
||||
await using var reader = await command.ExecuteReaderAsync();
|
||||
return await reader.ReadAsync() ? MapToEntity(reader) : null;
|
||||
}
|
||||
|
||||
protected override UserAccount MapToEntity(SqlDataReader reader)
|
||||
{
|
||||
return new UserAccount
|
||||
{
|
||||
UserAccountId = reader.GetGuid(0),
|
||||
Username = reader.GetString(1),
|
||||
FirstName = reader.GetString(2),
|
||||
LastName = reader.GetString(3),
|
||||
Email = reader.GetString(4),
|
||||
CreatedAt = reader.GetDateTime(5),
|
||||
UpdatedAt = reader.IsDBNull(6) ? null : reader.GetDateTime(6),
|
||||
DateOfBirth = reader.GetDateTime(7),
|
||||
Timer = reader.IsDBNull(8) ? null : (byte[])reader[8],
|
||||
UserAccountId = reader.GetGuid(reader.GetOrdinal("UserAccountId")),
|
||||
Username = reader.GetString(reader.GetOrdinal("Username")),
|
||||
FirstName = reader.GetString(reader.GetOrdinal("FirstName")),
|
||||
LastName = reader.GetString(reader.GetOrdinal("LastName")),
|
||||
Email = reader.GetString(reader.GetOrdinal("Email")),
|
||||
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt")),
|
||||
UpdatedAt = reader.IsDBNull(reader.GetOrdinal("UpdatedAt"))
|
||||
? null
|
||||
: reader.GetDateTime(reader.GetOrdinal("UpdatedAt")),
|
||||
DateOfBirth = reader.GetDateTime(reader.GetOrdinal("DateOfBirth")),
|
||||
Timer = reader.IsDBNull(reader.GetOrdinal("Timer"))
|
||||
? null
|
||||
: (byte[])reader["Timer"]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace DataAccessLayer.Sql
|
||||
{
|
||||
public class DatabaseHelper(string connectionString)
|
||||
{
|
||||
public void ExecuteRawSql(string query)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var connection = new SqlConnection(
|
||||
connectionString
|
||||
);
|
||||
|
||||
connection.Open();
|
||||
|
||||
using var command = new SqlCommand(query, connection);
|
||||
|
||||
command.CommandType = CommandType.Text;
|
||||
|
||||
using var reader = command.ExecuteReader();
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
for (var i = 0; i < reader.FieldCount; i++)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"{reader.GetName(i)}: {reader.GetValue(i)}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"An error occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
9
DataAccessLayer/Sql/ISqlConnectionFactory.cs
Normal file
9
DataAccessLayer/Sql/ISqlConnectionFactory.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace DataAccessLayer.Sql
|
||||
{
|
||||
public interface ISqlConnectionFactory
|
||||
{
|
||||
SqlConnection CreateConnection();
|
||||
}
|
||||
}
|
||||
26
WebAPI/Controllers/UserController.cs
Normal file
26
WebAPI/Controllers/UserController.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using BusinessLayer.Services;
|
||||
using DataAccessLayer.Entities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class UserController(IUserService userService) : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<UserAccount>>> GetAll([FromQuery] int? limit, [FromQuery] int? offset)
|
||||
{
|
||||
var users = await userService.GetAllAsync(limit, offset);
|
||||
return Ok(users);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public async Task<ActionResult<UserAccount>> GetById(Guid id)
|
||||
{
|
||||
var user = await userService.GetByIdAsync(id);
|
||||
if (user is null) return NotFound();
|
||||
return Ok(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
using BusinessLayer.Services;
|
||||
using DataAccessLayer.Entities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace WebAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/users")]
|
||||
public class UsersController : ControllerBase
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public UsersController(IUserService userService)
|
||||
{
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
// all users
|
||||
[HttpGet]
|
||||
public IActionResult GetAllUsers(
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] int? offset
|
||||
)
|
||||
{
|
||||
if (offset.HasValue && !limit.HasValue)
|
||||
{
|
||||
return BadRequest("Limit is required when offset is provided.");
|
||||
}
|
||||
|
||||
if (limit.HasValue && limit <= 0)
|
||||
{
|
||||
return BadRequest("Limit must be greater than zero.");
|
||||
}
|
||||
|
||||
if (offset.HasValue && offset < 0)
|
||||
{
|
||||
return BadRequest("Offset cannot be negative.");
|
||||
}
|
||||
|
||||
var users = _userService.GetAll(limit, offset);
|
||||
return Ok(users);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}")]
|
||||
public IActionResult GetUserById(Guid id)
|
||||
{
|
||||
var user = _userService.GetById(id);
|
||||
return user is null ? NotFound() : Ok(user);
|
||||
}
|
||||
|
||||
[HttpGet("username/{username}")]
|
||||
public IActionResult GetUserByUsername(string username)
|
||||
{
|
||||
var user = _userService.GetByUsername(username);
|
||||
return user is null ? NotFound() : Ok(user);
|
||||
}
|
||||
|
||||
[HttpGet("email/{email}")]
|
||||
public IActionResult GetUserByEmail(string email)
|
||||
{
|
||||
var user = _userService.GetByEmail(email);
|
||||
return user is null ? NotFound() : Ok(user);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult CreateUser([FromBody] UserAccount userAccount)
|
||||
{
|
||||
if (userAccount.UserAccountId == Guid.Empty)
|
||||
{
|
||||
userAccount.UserAccountId = Guid.NewGuid();
|
||||
}
|
||||
|
||||
_userService.Add(userAccount);
|
||||
return CreatedAtAction(
|
||||
nameof(GetUserById),
|
||||
new { id = userAccount.UserAccountId },
|
||||
userAccount
|
||||
);
|
||||
}
|
||||
|
||||
[HttpPut("{id:guid}")]
|
||||
public IActionResult UpdateUser(
|
||||
Guid id,
|
||||
[FromBody] UserAccount userAccount
|
||||
)
|
||||
{
|
||||
if (
|
||||
userAccount.UserAccountId != Guid.Empty
|
||||
&& userAccount.UserAccountId != id
|
||||
)
|
||||
{
|
||||
return BadRequest("UserAccountID does not match route id.");
|
||||
}
|
||||
|
||||
userAccount.UserAccountId = id;
|
||||
_userService.Update(userAccount);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpDelete("{id:guid}")]
|
||||
public IActionResult DeleteUser(Guid id)
|
||||
{
|
||||
_userService.Delete(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
25
WebAPI/Infrastructure/DefaultSqlConnectionFactory.cs
Normal file
25
WebAPI/Infrastructure/DefaultSqlConnectionFactory.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using DataAccessLayer.Sql;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace WebAPI.Infrastructure
|
||||
{
|
||||
public class DefaultSqlConnectionFactory : ISqlConnectionFactory
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
|
||||
public DefaultSqlConnectionFactory(IConfiguration configuration)
|
||||
{
|
||||
_connectionString =
|
||||
Environment.GetEnvironmentVariable("DB_CONNECTION_STRING")
|
||||
?? configuration.GetConnectionString("Default")
|
||||
?? throw new InvalidOperationException(
|
||||
"Database connection string not configured. Set DB_CONNECTION_STRING env var or ConnectionStrings:Default."
|
||||
);
|
||||
}
|
||||
|
||||
public SqlConnection CreateConnection()
|
||||
{
|
||||
return new SqlConnection(_connectionString);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using BusinessLayer.Services;
|
||||
using DataAccessLayer;
|
||||
using DataAccessLayer.Repositories;
|
||||
using DataAccessLayer.Sql;
|
||||
using WebAPI.Infrastructure;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -8,9 +9,11 @@ builder.Services.AddControllers();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
// Dependency Injection
|
||||
builder.Services.AddSingleton<ISqlConnectionFactory, DefaultSqlConnectionFactory>();
|
||||
builder.Services.AddScoped<IUserAccountRepository, UserAccountRepository>();
|
||||
builder.Services.AddScoped<IUserService, UserService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
|
||||
Reference in New Issue
Block a user