Solution Explorer
📁 th3chris.API
📄 Program.cs
📄 UsersController.cs
📄 Startup.cs
📁 Controllers
📄 AuthController.cs
📄 ProjectsController.cs
📄 OrdersController.cs
📁 Models
📄 User.cs
📄 Project.cs
📄 Order.cs
📄 BaseEntity.cs
📁 Services
📄 UserService.cs
📄 IUserService.cs
📄 AuthService.cs
📄 EmailService.cs
📁 Data
📄 AppDbContext.cs
📄 DbInitializer.cs
📁 DTOs
📄 UserDto.cs
📄 CreateUserRequest.cs
📄 UpdateUserRequest.cs
📁 Middleware
📄 ErrorHandlingMiddleware.cs
📄 LoggingMiddleware.cs
📁 Extensions
📄 ServiceExtensions.cs
📄 ControllerExtensions.cs
UsersController.cs
// File: UsersController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Threading.Tasks;
using th3chris.API.Models;
using th3chris.API.Services;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using System.Security.Claims;
using AutoMapper;
using FluentValidation;
namespace th3chris.API.Controllers
{
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
private readonly ILogger<UsersController> _logger;
private readonly IMapper _mapper;
private readonly IValidator<CreateUserRequest> _createValidator;
private readonly IValidator<UpdateUserRequest> _updateValidator;
public UsersController(
IUserService userService,
ILogger<UsersController> logger,
IMapper mapper,
IValidator<CreateUserRequest> createValidator,
IValidator<UpdateUserRequest> updateValidator)
{
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_createValidator = createValidator;
_updateValidator = updateValidator;
}
/// <summary>
/// Retrieves all users with pagination support
/// </summary>
/// <param name="pageNumber">Page number (default: 1)</param>
/// <param name="pageSize">Page size (default: 10, max: 100)</param>
/// <returns>Paginated list of users</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<PagedResult<UserDto>>> GetUsers(
[FromQuery] int pageNumber = 1,
[FromQuery] int pageSize = 10,
[FromQuery] string? search = null,
[FromQuery] string? sortBy = null,
[FromQuery] bool sortDescending = false)
{
try
{
if (pageNumber < 1)
return BadRequest("Page number must be greater than 0");
if (pageSize < 1 || pageSize > 100)
return BadRequest("Page size must be between 1 and 100");
_logger.LogInformation("Getting users - Page: {PageNumber}, Size: {PageSize}, Search: {Search}",
pageNumber, pageSize, search);
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var filter = new UserFilter
{
PageNumber = pageNumber,
PageSize = pageSize,
Search = search,
SortBy = sortBy,
SortDescending = sortDescending,
RequestingUserId = currentUserId
};
var users = await _userService.GetUsersAsync(filter);
var userDtos = _mapper.Map<IEnumerable<UserDto>>(users.Items);
var result = new PagedResult<UserDto>
{
Items = userDtos,
TotalCount = users.TotalCount,
PageNumber = pageNumber,
PageSize = pageSize,
TotalPages = (int)Math.Ceiling((double)users.TotalCount / pageSize)
};
return Ok(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while getting users");
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
/// <summary>
/// Retrieves a specific user by ID
/// </summary>
/// <param name="id">User ID</param>
/// <returns>User details</returns>
[HttpGet("{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<UserDetailDto>> GetUser(int id)
{
try
{
if (id <= 0)
return BadRequest("Invalid user ID");
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("Getting user {UserId} requested by {CurrentUserId}", id, currentUserId);
var user = await _userService.GetUserByIdAsync(id, currentUserId);
if (user == null)
{
_logger.LogWarning("User {UserId} not found or access denied for {CurrentUserId}", id, currentUserId);
return NotFound($"User with ID {id} not found");
}
var userDto = _mapper.Map<UserDetailDto>(user);
return Ok(userDto);
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, "Unauthorized access attempt for user {UserId}", id);
return Forbid();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while getting user {UserId}", id);
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
/// <summary>
/// Creates a new user
/// </summary>
/// <param name="request">User creation request</param>
/// <returns>Created user</returns>
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<ActionResult<UserDto>> CreateUser([FromBody] CreateUserRequest request)
{
try
{
if (request == null)
return BadRequest("Request body is required");
var validationResult = await _createValidator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors.Select(e => e.ErrorMessage);
return BadRequest(new { Errors = errors });
}
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("Creating new user {Email} by {CurrentUserId}", request.Email, currentUserId);
var user = _mapper.Map<User>(request);
user.CreatedBy = currentUserId;
user.CreatedAt = DateTime.UtcNow;
var createdUser = await _userService.CreateUserAsync(user);
var userDto = _mapper.Map<UserDto>(createdUser);
return CreatedAtAction(nameof(GetUser), new { id = createdUser.Id }, userDto);
}
catch (DuplicateUserException ex)
{
_logger.LogWarning(ex, "Attempt to create duplicate user {Email}", request?.Email);
return Conflict(new { Message = "User with this email already exists" });
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while creating user");
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
/// <summary>
/// Updates an existing user
/// </summary>
/// <param name="id">User ID</param>
/// <param name="request">User update request</param>
/// <returns>Updated user</returns>
[HttpPut("{id:int}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<UserDto>> UpdateUser(int id, [FromBody] UpdateUserRequest request)
{
try
{
if (id <= 0)
return BadRequest("Invalid user ID");
if (request == null)
return BadRequest("Request body is required");
var validationResult = await _updateValidator.ValidateAsync(request);
if (!validationResult.IsValid)
{
var errors = validationResult.Errors.Select(e => e.ErrorMessage);
return BadRequest(new { Errors = errors });
}
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("Updating user {UserId} by {CurrentUserId}", id, currentUserId);
var existingUser = await _userService.GetUserByIdAsync(id, currentUserId);
if (existingUser == null)
{
_logger.LogWarning("User {UserId} not found for update by {CurrentUserId}", id, currentUserId);
return NotFound($"User with ID {id} not found");
}
_mapper.Map(request, existingUser);
existingUser.UpdatedBy = currentUserId;
existingUser.UpdatedAt = DateTime.UtcNow;
var updatedUser = await _userService.UpdateUserAsync(existingUser);
var userDto = _mapper.Map<UserDto>(updatedUser);
return Ok(userDto);
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, "Unauthorized update attempt for user {UserId}", id);
return Forbid();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while updating user {UserId}", id);
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
/// <summary>
/// Deletes a user by ID
/// </summary>
/// <param name="id">User ID</param>
/// <returns>No content on success</returns>
[HttpDelete("{id:int}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<IActionResult> DeleteUser(int id)
{
try
{
if (id <= 0)
return BadRequest("Invalid user ID");
var currentUserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("Deleting user {UserId} by {CurrentUserId}", id, currentUserId);
var success = await _userService.DeleteUserAsync(id, currentUserId);
if (!success)
{
_logger.LogWarning("User {UserId} not found for deletion by {CurrentUserId}", id, currentUserId);
return NotFound($"User with ID {id} not found");
}
_logger.LogInformation("User {UserId} successfully deleted by {CurrentUserId}", id, currentUserId);
return NoContent();
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, "Unauthorized deletion attempt for user {UserId}", id);
return Forbid();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while deleting user {UserId}", id);
return StatusCode(StatusCodes.Status500InternalServerError,
"An error occurred while processing your request");
}
}
}
}
// File: UserService.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using th3chris.API.Models;
using th3chris.API.Data;
using th3chris.API.Exceptions;
namespace th3chris.API.Services
{
public class UserService : IUserService
{
private readonly AppDbContext _context;
private readonly ILogger<UserService> _logger;
public UserService(AppDbContext context, ILogger<UserService> logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<PagedResult<User>> GetUsersAsync(UserFilter filter)
{
var query = _context.Users.AsQueryable();
if (!string.IsNullOrWhiteSpace(filter.Search))
{
var searchTerm = filter.Search.ToLower();
query = query.Where(u => u.Email.ToLower().Contains(searchTerm) ||
u.FirstName.ToLower().Contains(searchTerm) ||
u.LastName.ToLower().Contains(searchTerm));
}
var totalCount = await query.CountAsync();
if (!string.IsNullOrWhiteSpace(filter.SortBy))
{
query = ApplySorting(query, filter.SortBy, filter.SortDescending);
}
else
{
query = query.OrderBy(u => u.LastName).ThenBy(u => u.FirstName);
}
var users = await query
.Skip((filter.PageNumber - 1) * filter.PageSize)
.Take(filter.PageSize)
.ToListAsync();
return new PagedResult<User>
{
Items = users,
TotalCount = totalCount
};
}
}
}
Test Explorer
GetUsers_ReturnsOk
GetUsers_WithPagination
GetUsers_WithSearch
GetUser_ValidId_ReturnsUser
GetUser_InvalidId_NotFound
CreateUser_ValidData
CreateUser_DuplicateEmail
CreateUser_InvalidData
UpdateUser_ValidData
UpdateUser_NotFound
UpdateUser_Unauthorized
DeleteUser_Success
DeleteUser_NotFound
DeleteUser_Unauthorized
Auth_Login_ValidCredentials
Auth_Login_InvalidPassword
Auth_Register_NewUser
Email_SendWelcome_Success
Validation_User_RequiredFields
Validation_Email_Format

Projektanfrage

Zusammenarbeit

Sie suchen einen erfahrenen Softwareentwickler und IT-Consultant für Ihr Projekt? Ich unterstütze Sie gerne bei der Umsetzung Ihrer individuellen Anforderungen – effizient, remote und mit Leidenschaft für neue Technologien.

Meine Arbeitsweise

🏠100% Remote-Arbeit

Ich arbeite ausschließlich remote, um maximale Effizienz zu gewährleisten. Lange Autobahnfahrten sind vergeudete Zeit – diese investiere ich lieber in Ihr Projekt. Vor-Ort-Besuche sind selbstverständlich möglich, wenn es wichtige Gründe gibt: Kennenlernen der Firma, Einblicke in Maschinen oder Prozesse verstehen.

💬Digitale Kommunikation

Kommunikation läuft vollständig digital – so wie Sie es bevorzugen: Slack, Microsoft Teams, Zoom, Telefon, E-Mail. Flexible Erreichbarkeit und schnelle Abstimmungswege sind für mich selbstverständlich.

🛠️Eigene Infrastruktur

Ich arbeite mit eigener Hardware, eigenen Tools und eigenen Lizenzen. Sie müssen mir lediglich Zugang zu Quellcode, Dokumentation oder Infrastrukturen bereitstellen – je nach Projektumfang. Keine zusätzlichen Lizenzkosten oder Hardware-Beschaffung für Sie.

🚀Technologie-Enthusiast

Neue Technologien begeistern mich! Ob etablierte Stacks oder innovative Tools – ich arbeite mich gerne in neue Bereiche ein. Continuous Learning ist für mich nicht nur ein Buzzword, sondern gelebte Praxis. Ihr Projekt profitiert von aktuellen Best Practices und modernen Lösungsansätzen.

Was ich mitbringe

Expertise

Langjährige Erfahrung in Softwareentwicklung, Architektur und DevOps. Details zu meinem Tech-Stack finden Sie auf der Startseite oder fragen Sie gerne im Chat nach.

Flexibilität

Anpassung an Ihre Prozesse, Tools und Arbeitsweisen. Ob agile Sprints, Wasserfall oder hybride Modelle – ich füge mich nahtlos in Ihr Team ein.

Pragmatismus

Lösungsorientiertes Denken statt theoretischer Diskussionen. Praktische Umsetzungen, die funktionieren und wartbar sind.

Lernbereitschaft

Offenheit für neue Technologien, Frameworks und Methoden. Ihr Projekt nutzt etwas Neues? Perfekt – ich bin dabei!

Wie läuft eine Zusammenarbeit ab?

1. Erstgespräch

Wir besprechen Ihre Anforderungen, Ziele und Rahmenbedingungen in einem unverbindlichen Gespräch.

2. Angebot

Sie erhalten ein individuelles Angebot mit Leistungsumfang, Zeitplan und Konditionen.

3. Vertragsabschluss

Bei Interesse schließen wir einen individuellen Dienstleistungsvertrag ab, der alle Details regelt.

4. Umsetzung

Gemeinsam setzen wir Ihr Projekt um – mit regelmäßiger Kommunikation und transparenter Zusammenarbeit.

Kontaktaufnahme

Sie haben mehrere Möglichkeiten, mit mir in Kontakt zu treten:

💬

Schnelle Ersteinschätzung im Chat

Sie können Ihre Projektanforderungen direkt im Chat hochladen oder beschreiben! Der KI-gestützte Chat analysiert Ihre Anforderungen und gibt Ihnen eine erste Einschätzung, wie gut ich als Kandidat für Ihr Projekt geeignet bin.

Tipp: Laden Sie ein Dokument mit Ihren Projektdetails hoch oder beschreiben Sie Ihr Vorhaben – Sie erhalten sofort eine fundierte Ersteinschätzung meiner Eignung für Ihr Projekt.

📧

Direkte E-Mail-Anfrage

Senden Sie mir eine E-Mail mit einer kurzen Beschreibung Ihres Projekts. Ich melde mich zeitnah bei Ihnen für ein Erstgespräch.

hello@th3chris.com

Wichtige Hinweise

Hinweis zu Verträgen: Konkrete Projekte werden über individuelle Dienstleistungsverträge abgewickelt. Allgemeine Geschäftsbedingungen (AGB) sind für diese Website nicht erforderlich, da hier keine direkten Online-Vertragsabschlüsse stattfinden. Jedes Projekt erhält einen maßgeschneiderten Vertrag mit allen relevanten Konditionen.

Häufige Fragen

Wie schnell können Sie starten?

Die Verfügbarkeit hängt von der aktuellen Auslastung ab. Bitte fragen Sie meine aktuelle Verfügbarkeit direkt im Chat, per E-Mail oder telefonisch an – ich gebe Ihnen zeitnah Auskunft über mögliche Starttermine.

Müssen Sie vor Ort arbeiten?

Nein, ich arbeite ausschließlich remote. Vor-Ort-Besuche sind möglich, wenn es wichtige Gründe gibt (Kennenlernen, Maschinenbesichtigung, Prozessverständnis), aber nicht für die reguläre Projektarbeit.

Wie wird abgerechnet?

Ich arbeite niemals auf Festpreis-Basis, sondern rechne fair nach tatsächlich geleisteter Arbeit ab – entweder auf Stunden- oder Tagessatzbasis. Für transparentes Zeiterfassung nutze ich Toggl (minutengenaues Tracking). Bei ticketbasierter Arbeit buche ich die Zeiten direkt auf die jeweiligen Tickets. Mit jeder Rechnung erhalten Sie eine detaillierte Aufstellung, welche Zeit in welche Aufgabe geflossen ist.

Welche Branchen bedienen Sie?

Branchenunabhängig – von Startups bis Konzerne, von E-Commerce bis Industrie 4.0. Jede Branche bringt spannende technische Herausforderungen mit sich.

Was passiert, wenn Sie eine Technologie noch nicht kennen?

Dann arbeite ich mich ein! Neue Technologien sind für mich Chance statt Hindernis. Mit fundiertem Grundwissen und schneller Auffassungsgabe komme ich zügig auf Projektgeschwindigkeit.

Brauche ich spezielle Hardware oder Lizenzen für Sie?

Nein, ich arbeite mit eigener Hardware und eigenen Lizenzen. Sie stellen mir lediglich Zugang zu Ihren Systemen bereit (Quellcode-Repositories, Dokumentation, Infrastrukturen).