Update repository, seed and service layers

This commit is contained in:
Aaron Po
2026-01-15 14:48:18 -05:00
parent b8cd855916
commit c5aaf8cd05
15 changed files with 322 additions and 412 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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(),
};
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View 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);
}
}

View File

@@ -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();
using SqlDataReader reader = command.ExecuteReader();
return reader.Read() ? MapToEntity(reader) : null;
}
public void Update(UserAccount userAccount)
{
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();
}
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)
await using var connection = await CreateConnection();
await using var command = new SqlCommand("usp_GetUserAccountById", connection)
{
throw new ArgumentOutOfRangeException(
nameof(limit),
"Limit must be greater than zero."
);
}
CommandType = CommandType.StoredProcedure
};
if (offset < 0)
{
throw new ArgumentOutOfRangeException(
nameof(offset),
"Offset cannot be negative."
);
}
command.Parameters.Add("@UserAccountId", SqlDbType.UniqueIdentifier).Value = id;
if (offset.HasValue && !limit.HasValue)
{
throw new ArgumentOutOfRangeException(
nameof(offset),
"Offset cannot be provided without a limit."
);
}
await using var reader = await command.ExecuteReaderAsync();
return await reader.ReadAsync() ? MapToEntity(reader) : null;
}
public override async Task<IEnumerable<UserAccount>> GetAll(int? limit, int? offset)
{
await using var connection = await CreateConnection();
await using var command = new SqlCommand("usp_GetAllUserAccounts", connection);
command.CommandType = CommandType.StoredProcedure;
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"]
};
}
}

View File

@@ -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}");
}
}
}
}

View File

@@ -0,0 +1,9 @@
using Microsoft.Data.SqlClient;
namespace DataAccessLayer.Sql
{
public interface ISqlConnectionFactory
{
SqlConnection CreateConnection();
}
}

View 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);
}
}
}

View File

@@ -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();
}
}
}

View 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);
}
}
}

View File

@@ -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())