diff --git a/Webshop.Api/Controllers/Customers/AddressesController.cs b/Webshop.Api/Controllers/Customers/AddressesController.cs index 9bf4f8c..27d91c0 100644 --- a/Webshop.Api/Controllers/Customers/AddressesController.cs +++ b/Webshop.Api/Controllers/Customers/AddressesController.cs @@ -1,13 +1,14 @@ // src/Webshop.Api/Controllers/Customer/AddressesController.cs using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; +using Webshop.Application; using Webshop.Application.DTOs.Customers; using Webshop.Application.Services.Customers; -using Webshop.Application.Services.Customers.Interfaces; namespace Webshop.Api.Controllers.Customer { @@ -24,69 +25,123 @@ namespace Webshop.Api.Controllers.Customer } [HttpGet] - public async Task>> GetMyAddresses() + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetMyAddresses() { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); - var addresses = await _addressService.GetMyAddressesAsync(userId); - return Ok(addresses); + var result = await _addressService.GetMyAddressesAsync(userId!); + return Ok(result.Value); + } + + [HttpGet("{id}")] + [ProducesResponseType(typeof(AddressDto), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetMyAddressById(Guid id) + { + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + var result = await _addressService.GetMyAddressByIdAsync(id, userId!); + + return result.Type switch + { + ServiceResultType.Success => Ok(result.Value), + ServiceResultType.Forbidden => Forbid(), + ServiceResultType.NotFound => NotFound(new { Message = result.ErrorMessage }), + _ => StatusCode(StatusCodes.Status500InternalServerError, "Ein unerwarteter Fehler ist aufgetreten.") + }; } [HttpPost] - public async Task> CreateAddress([FromBody] CreateAddressDto addressDto) + [ProducesResponseType(typeof(AddressDto), StatusCodes.Status201Created)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + public async Task CreateAddress([FromBody] CreateAddressDto addressDto) { - var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); - var (createdAddress, errorMessage) = await _addressService.CreateAddressAsync(addressDto, userId); - - if (createdAddress == null) return BadRequest(new { Message = errorMessage }); - - return CreatedAtAction(nameof(GetMyAddresses), new { id = createdAddress.Id }, createdAddress); - } - - [HttpPut("{id}")] - public async Task UpdateAddress(Guid id, [FromBody] UpdateAddressDto addressDto) - { - if (id != addressDto.Id) return BadRequest("ID in URL und Body stimmen nicht überein."); if (!ModelState.IsValid) return BadRequest(ModelState); var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); - var (success, errorMessage) = await _addressService.UpdateAddressAsync(addressDto, userId); + var result = await _addressService.CreateAddressAsync(addressDto, userId!); - if (!success) return BadRequest(new { Message = errorMessage }); + return result.Type switch + { + ServiceResultType.Success => CreatedAtAction(nameof(GetMyAddressById), new { id = result.Value!.Id }, result.Value), + _ => BadRequest(new { Message = result.ErrorMessage }) + }; + } - return NoContent(); + [HttpPut("{id}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task UpdateAddress(Guid id, [FromBody] UpdateAddressDto addressDto) + { + if (id != addressDto.Id) return BadRequest(new { Message = "ID in der URL und im Body stimmen nicht überein." }); + if (!ModelState.IsValid) return BadRequest(ModelState); + + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); + var result = await _addressService.UpdateAddressAsync(addressDto, userId!); + + return result.Type switch + { + ServiceResultType.Success => NoContent(), + ServiceResultType.Forbidden => Forbid(), + ServiceResultType.NotFound => NotFound(new { Message = result.ErrorMessage }), + _ => BadRequest(new { Message = result.ErrorMessage }) + }; } [HttpDelete("{id}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task DeleteAddress(Guid id) { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); - var (success, errorMessage) = await _addressService.DeleteAddressAsync(id, userId); + var result = await _addressService.DeleteAddressAsync(id, userId!); - if (!success) return BadRequest(new { Message = errorMessage }); - - return NoContent(); + return result.Type switch + { + ServiceResultType.Success => NoContent(), + ServiceResultType.Forbidden => Forbid(), + ServiceResultType.NotFound => NotFound(new { Message = result.ErrorMessage }), + _ => BadRequest(new { Message = result.ErrorMessage }) + }; } [HttpPost("default-shipping/{id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task SetDefaultShippingAddress(Guid id) { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); - var (success, errorMessage) = await _addressService.SetDefaultShippingAddressAsync(id, userId); + var result = await _addressService.SetDefaultShippingAddressAsync(id, userId!); - if (!success) return BadRequest(new { Message = errorMessage }); - - return Ok(); + return result.Type switch + { + ServiceResultType.Success => Ok(), + ServiceResultType.Forbidden => Forbid(), + ServiceResultType.NotFound => NotFound(new { Message = result.ErrorMessage }), + _ => BadRequest(new { Message = result.ErrorMessage }) + }; } [HttpPost("default-billing/{id}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task SetDefaultBillingAddress(Guid id) { var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); - var (success, errorMessage) = await _addressService.SetDefaultBillingAddressAsync(id, userId); + var result = await _addressService.SetDefaultBillingAddressAsync(id, userId!); - if (!success) return BadRequest(new { Message = errorMessage }); - - return Ok(); + return result.Type switch + { + ServiceResultType.Success => Ok(), + ServiceResultType.Forbidden => Forbid(), + ServiceResultType.NotFound => NotFound(new { Message = result.ErrorMessage }), + _ => BadRequest(new { Message = result.ErrorMessage }) + }; } } } \ No newline at end of file diff --git a/Webshop.Application/Services/Customers/AddressService.cs b/Webshop.Application/Services/Customers/AddressService.cs index 9954341..8469fce 100644 --- a/Webshop.Application/Services/Customers/AddressService.cs +++ b/Webshop.Application/Services/Customers/AddressService.cs @@ -3,10 +3,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Webshop.Application; using Webshop.Application.DTOs.Customers; using Webshop.Domain.Entities; using Webshop.Domain.Interfaces; -using Webshop.Infrastructure.Data; // Für direkten Zugriff auf den DbContext (optional) namespace Webshop.Application.Services.Customers { @@ -14,56 +14,61 @@ namespace Webshop.Application.Services.Customers { private readonly IAddressRepository _addressRepository; private readonly ICustomerRepository _customerRepository; - private readonly ApplicationDbContext _context; // Für komplexe Abfragen - public AddressService(IAddressRepository addressRepository, ICustomerRepository customerRepository, ApplicationDbContext context) + public AddressService(IAddressRepository addressRepository, ICustomerRepository customerRepository) { _addressRepository = addressRepository; _customerRepository = customerRepository; - _context = context; } - public async Task> GetMyAddressesAsync(string userId) + public async Task>> GetMyAddressesAsync(string userId) { var customer = await _customerRepository.GetByUserIdAsync(userId); - if (customer == null) return new List(); + if (customer == null) + { + // If the customer profile doesn't exist for a valid user, return an empty list. + return ServiceResult.Ok>(new List()); + } var addresses = await _addressRepository.GetAllForCustomerAsync(customer.Id); - return addresses.Select(a => new AddressDto - { - Id = a.Id, - FirstName = a.FirstName, - LastName = a.LastName, - Street = a.Street, - HouseNumber = a.HouseNumber, - City = a.City, - PostalCode = a.PostalCode, - Country = a.Country, - Type = a.Type - }).ToList(); + return ServiceResult.Ok(addresses.Select(MapToDto)); } - public async Task GetMyAddressByIdAsync(Guid addressId, string userId) + public async Task> GetMyAddressByIdAsync(Guid addressId, string userId) { var customer = await _customerRepository.GetByUserIdAsync(userId); - if (customer == null) return null; + if (customer == null) + { + return ServiceResult.Fail(ServiceResultType.NotFound, "Kundenprofil nicht gefunden."); + } var address = await _addressRepository.GetByIdAsync(addressId); - if (address == null || address.CustomerId != customer.Id) return null; + if (address == null) + { + return ServiceResult.Fail(ServiceResultType.NotFound, $"Adresse mit ID '{addressId}' nicht gefunden."); + } - return new AddressDto { /* ... Mapping ... */ }; + if (address.CustomerId != customer.Id) + { + return ServiceResult.Fail(ServiceResultType.Forbidden, "Zugriff auf diese Adresse verweigert."); + } + + return ServiceResult.Ok(MapToDto(address)); } - public async Task<(AddressDto? CreatedAddress, string? ErrorMessage)> CreateAddressAsync(CreateAddressDto addressDto, string userId) + public async Task> CreateAddressAsync(CreateAddressDto addressDto, string userId) { var customer = await _customerRepository.GetByUserIdAsync(userId); - if (customer == null) return (null, "Kunde nicht gefunden."); + if (customer == null) + { + return ServiceResult.Fail(ServiceResultType.NotFound, "Kundenprofil nicht gefunden."); + } var newAddress = new Address { CustomerId = customer.Id, - FirstName = customer.FirstName, // Vorerst vom Kundenprofil übernehmen - LastName = customer.LastName, + FirstName = addressDto.FirstName ?? customer.FirstName, + LastName = addressDto.LastName ?? customer.LastName, Street = addressDto.Street, HouseNumber = addressDto.HouseNumber, City = addressDto.City, @@ -73,21 +78,17 @@ namespace Webshop.Application.Services.Customers }; await _addressRepository.AddAsync(newAddress); - - var createdDto = new AddressDto { Id = newAddress.Id, /* ... Mapping ... */ }; - return (createdDto, null); + return ServiceResult.Ok(MapToDto(newAddress)); } - public async Task<(bool Success, string? ErrorMessage)> UpdateAddressAsync(UpdateAddressDto addressDto, string userId) + public async Task UpdateAddressAsync(UpdateAddressDto addressDto, string userId) { var customer = await _customerRepository.GetByUserIdAsync(userId); - if (customer == null) return (false, "Kunde nicht gefunden."); + if (customer == null) return ServiceResult.Fail(ServiceResultType.NotFound, "Kundenprofil nicht gefunden."); var address = await _addressRepository.GetByIdAsync(addressDto.Id); - if (address == null || address.CustomerId != customer.Id) - { - return (false, "Adresse nicht gefunden oder gehört nicht zum aktuellen Benutzer."); - } + if (address == null) return ServiceResult.Fail(ServiceResultType.NotFound, "Adresse nicht gefunden."); + if (address.CustomerId != customer.Id) return ServiceResult.Fail(ServiceResultType.Forbidden, "Zugriff auf diese Adresse verweigert."); address.FirstName = addressDto.FirstName; address.LastName = addressDto.LastName; @@ -99,54 +100,64 @@ namespace Webshop.Application.Services.Customers address.Type = addressDto.Type; await _addressRepository.UpdateAsync(address); - return (true, null); + return ServiceResult.Ok(); } - public async Task<(bool Success, string? ErrorMessage)> DeleteAddressAsync(Guid addressId, string userId) + public async Task DeleteAddressAsync(Guid addressId, string userId) { var customer = await _customerRepository.GetByUserIdAsync(userId); - if (customer == null) return (false, "Kunde nicht gefunden."); + if (customer == null) return ServiceResult.Fail(ServiceResultType.NotFound, "Kundenprofil nicht gefunden."); var address = await _addressRepository.GetByIdAsync(addressId); - if (address == null || address.CustomerId != customer.Id) - { - return (false, "Adresse nicht gefunden oder gehört nicht zum aktuellen Benutzer."); - } + if (address == null) return ServiceResult.Fail(ServiceResultType.NotFound, "Adresse nicht gefunden."); + if (address.CustomerId != customer.Id) return ServiceResult.Fail(ServiceResultType.Forbidden, "Zugriff auf diese Adresse verweigert."); await _addressRepository.DeleteAsync(addressId); - return (true, null); + return ServiceResult.Ok(); } - public async Task<(bool Success, string? ErrorMessage)> SetDefaultShippingAddressAsync(Guid addressId, string userId) + public async Task SetDefaultShippingAddressAsync(Guid addressId, string userId) { var customer = await _customerRepository.GetByUserIdAsync(userId); - if (customer == null) return (false, "Kunde nicht gefunden."); + if (customer == null) return ServiceResult.Fail(ServiceResultType.NotFound, "Kundenprofil nicht gefunden."); var address = await _addressRepository.GetByIdAsync(addressId); - if (address == null || address.CustomerId != customer.Id) - { - return (false, "Adresse nicht gefunden oder gehört nicht zum aktuellen Benutzer."); - } + if (address == null) return ServiceResult.Fail(ServiceResultType.NotFound, "Adresse nicht gefunden."); + if (address.CustomerId != customer.Id) return ServiceResult.Fail(ServiceResultType.Forbidden, "Zugriff auf diese Adresse verweigert."); customer.DefaultShippingAddressId = addressId; await _customerRepository.UpdateAsync(customer); - return (true, null); + return ServiceResult.Ok(); } - public async Task<(bool Success, string? ErrorMessage)> SetDefaultBillingAddressAsync(Guid addressId, string userId) + public async Task SetDefaultBillingAddressAsync(Guid addressId, string userId) { var customer = await _customerRepository.GetByUserIdAsync(userId); - if (customer == null) return (false, "Kunde nicht gefunden."); + if (customer == null) return ServiceResult.Fail(ServiceResultType.NotFound, "Kundenprofil nicht gefunden."); var address = await _addressRepository.GetByIdAsync(addressId); - if (address == null || address.CustomerId != customer.Id) - { - return (false, "Adresse nicht gefunden oder gehört nicht zum aktuellen Benutzer."); - } + if (address == null) return ServiceResult.Fail(ServiceResultType.NotFound, "Adresse nicht gefunden."); + if (address.CustomerId != customer.Id) return ServiceResult.Fail(ServiceResultType.Forbidden, "Zugriff auf diese Adresse verweigert."); customer.DefaultBillingAddressId = addressId; await _customerRepository.UpdateAsync(customer); - return (true, null); + return ServiceResult.Ok(); + } + + private AddressDto MapToDto(Address address) + { + return new AddressDto + { + Id = address.Id, + FirstName = address.FirstName, + LastName = address.LastName, + Street = address.Street, + HouseNumber = address.HouseNumber, + City = address.City, + PostalCode = address.PostalCode, + Country = address.Country, + Type = address.Type + }; } } } \ No newline at end of file diff --git a/Webshop.Application/Services/Customers/Interfaces/IAddressService.cs b/Webshop.Application/Services/Customers/Interfaces/IAddressService.cs index 1a95921..4c52919 100644 --- a/Webshop.Application/Services/Customers/Interfaces/IAddressService.cs +++ b/Webshop.Application/Services/Customers/Interfaces/IAddressService.cs @@ -2,18 +2,19 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Webshop.Application; using Webshop.Application.DTOs.Customers; namespace Webshop.Application.Services.Customers { public interface IAddressService { - Task> GetMyAddressesAsync(string userId); - Task GetMyAddressByIdAsync(Guid addressId, string userId); - Task<(AddressDto? CreatedAddress, string? ErrorMessage)> CreateAddressAsync(CreateAddressDto addressDto, string userId); - Task<(bool Success, string? ErrorMessage)> UpdateAddressAsync(UpdateAddressDto addressDto, string userId); - Task<(bool Success, string? ErrorMessage)> DeleteAddressAsync(Guid addressId, string userId); - Task<(bool Success, string? ErrorMessage)> SetDefaultShippingAddressAsync(Guid addressId, string userId); - Task<(bool Success, string? ErrorMessage)> SetDefaultBillingAddressAsync(Guid addressId, string userId); + Task>> GetMyAddressesAsync(string userId); + Task> GetMyAddressByIdAsync(Guid addressId, string userId); + Task> CreateAddressAsync(CreateAddressDto addressDto, string userId); + Task UpdateAddressAsync(UpdateAddressDto addressDto, string userId); + Task DeleteAddressAsync(Guid addressId, string userId); + Task SetDefaultShippingAddressAsync(Guid addressId, string userId); + Task SetDefaultBillingAddressAsync(Guid addressId, string userId); } } \ No newline at end of file