From 1692fe198ef904bc1c95e9a64e769f48e9d541c2 Mon Sep 17 00:00:00 2001 From: "Tizian.Breuch" Date: Tue, 12 Aug 2025 11:07:15 +0200 Subject: [PATCH] shop info --- .../Admin/AdminShopInfoController.cs | 40 ++++++++++ .../Controllers/Public/ShopInfoController.cs | 29 ++++++++ Webshop.Api/Program.cs | 3 + .../DTOs/Shop/AdminShopInfoDto.cs | 47 ++++++++++++ .../DTOs/Shop/PublicShopInfoDto.cs | 19 +++++ .../Services/Admin/AdminShopInfoService.cs | 74 +++++++++++++++++++ .../Admin/Interfaces/IAdminShopInfoService.cs | 12 +++ .../Public/Interfaces/IShopInfoService.cs | 11 +++ .../Services/Public/ShopInfoService.cs | 43 +++++++++++ Webshop.Domain/Entities/ShopInfo.cs | 58 +++++++++++++++ .../Interfaces/IShopInfoRepository.cs | 13 ++++ .../Data/ApplicationDbContext.cs | 1 + .../Repositories/ShopInfoRepository.cs | 37 ++++++++++ 13 files changed, 387 insertions(+) create mode 100644 Webshop.Api/Controllers/Admin/AdminShopInfoController.cs create mode 100644 Webshop.Api/Controllers/Public/ShopInfoController.cs create mode 100644 Webshop.Application/DTOs/Shop/AdminShopInfoDto.cs create mode 100644 Webshop.Application/DTOs/Shop/PublicShopInfoDto.cs create mode 100644 Webshop.Application/Services/Admin/AdminShopInfoService.cs create mode 100644 Webshop.Application/Services/Admin/Interfaces/IAdminShopInfoService.cs create mode 100644 Webshop.Application/Services/Public/Interfaces/IShopInfoService.cs create mode 100644 Webshop.Application/Services/Public/ShopInfoService.cs create mode 100644 Webshop.Domain/Entities/ShopInfo.cs create mode 100644 Webshop.Domain/Interfaces/IShopInfoRepository.cs create mode 100644 Webshop.Infrastructure/Repositories/ShopInfoRepository.cs diff --git a/Webshop.Api/Controllers/Admin/AdminShopInfoController.cs b/Webshop.Api/Controllers/Admin/AdminShopInfoController.cs new file mode 100644 index 0000000..a319c41 --- /dev/null +++ b/Webshop.Api/Controllers/Admin/AdminShopInfoController.cs @@ -0,0 +1,40 @@ +// src/Webshop.Api/Controllers/Admin/AdminShopInfoController.cs +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Webshop.Application.DTOs.Shop; +using Webshop.Application.Services.Admin; + +namespace Webshop.Api.Controllers.Admin +{ + [ApiController] + [Route("api/v1/admin/[controller]")] + [Authorize(Roles = "Admin")] + public class AdminShopInfoController : ControllerBase + { + private readonly IAdminShopInfoService _shopInfoService; + + public AdminShopInfoController(IAdminShopInfoService shopInfoService) + { + _shopInfoService = shopInfoService; + } + + [HttpGet] + public async Task> GetShopInfo() + { + var info = await _shopInfoService.GetShopInfoAsync(); + return Ok(info); + } + + [HttpPut] + public async Task UpdateShopInfo([FromBody] AdminShopInfoDto shopInfoDto) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + await _shopInfoService.UpdateShopInfoAsync(shopInfoDto); + return NoContent(); + } + } +} \ No newline at end of file diff --git a/Webshop.Api/Controllers/Public/ShopInfoController.cs b/Webshop.Api/Controllers/Public/ShopInfoController.cs new file mode 100644 index 0000000..ee6df01 --- /dev/null +++ b/Webshop.Api/Controllers/Public/ShopInfoController.cs @@ -0,0 +1,29 @@ +// src/Webshop.Api/Controllers/Public/ShopInfoController.cs +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; +using Webshop.Application.DTOs.Shop; +using Webshop.Application.Services.Public; + +namespace Webshop.Api.Controllers.Public +{ + [ApiController] + [Route("api/v1/public/[]controller")] + [AllowAnonymous] + public class ShopInfoController : ControllerBase + { + private readonly IShopInfoService _shopInfoService; + + public ShopInfoController(IShopInfoService shopInfoService) + { + _shopInfoService = shopInfoService; + } + + [HttpGet] + public async Task> GetPublicShopInfo() + { + var info = await _shopInfoService.GetPublicShopInfoAsync(); + return Ok(info); + } + } +} \ No newline at end of file diff --git a/Webshop.Api/Program.cs b/Webshop.Api/Program.cs index 01f3088..37d4011 100644 --- a/Webshop.Api/Program.cs +++ b/Webshop.Api/Program.cs @@ -105,6 +105,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); // Services builder.Services.AddScoped(); @@ -125,6 +126,8 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); // Controller und API-Infrastruktur builder.Services.AddControllers() diff --git a/Webshop.Application/DTOs/Shop/AdminShopInfoDto.cs b/Webshop.Application/DTOs/Shop/AdminShopInfoDto.cs new file mode 100644 index 0000000..b6c9bde --- /dev/null +++ b/Webshop.Application/DTOs/Shop/AdminShopInfoDto.cs @@ -0,0 +1,47 @@ +// src/Webshop.Application/DTOs/Admin/AdminShopInfoDto.cs +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Application.DTOs.Shop +{ + public class AdminShopInfoDto + { + [Required, MaxLength(255)] + public string ShopName { get; set; } = string.Empty; + + [MaxLength(500)] + public string? Slogan { get; set; } + + [Required, EmailAddress, MaxLength(255)] + public string ContactEmail { get; set; } = string.Empty; + + [Phone, MaxLength(50)] + public string? PhoneNumber { get; set; } + + [MaxLength(255)] + public string? Street { get; set; } + + [MaxLength(100)] + public string? City { get; set; } + + [MaxLength(20)] + public string? PostalCode { get; set; } + + [MaxLength(100)] + public string? Country { get; set; } + + [MaxLength(50)] + public string? VatNumber { get; set; } + + [MaxLength(100)] + public string? CompanyRegistrationNumber { get; set; } + + [Url, MaxLength(255)] + public string? FacebookUrl { get; set; } + + [Url, MaxLength(255)] + public string? InstagramUrl { get; set; } + + [Url, MaxLength(255)] + public string? TwitterUrl { get; set; } + } +} \ No newline at end of file diff --git a/Webshop.Application/DTOs/Shop/PublicShopInfoDto.cs b/Webshop.Application/DTOs/Shop/PublicShopInfoDto.cs new file mode 100644 index 0000000..c1815d4 --- /dev/null +++ b/Webshop.Application/DTOs/Shop/PublicShopInfoDto.cs @@ -0,0 +1,19 @@ +// src/Webshop.Application/DTOs/Shop/PublicShopInfoDto.cs +namespace Webshop.Application.DTOs.Shop +{ + // Dieses DTO enthält nur die Informationen, die ein normaler Kunde sehen soll. + public class PublicShopInfoDto + { + public string ShopName { get; set; } = string.Empty; + public string? Slogan { get; set; } + public string ContactEmail { get; set; } = string.Empty; + public string? PhoneNumber { get; set; } + public string? Street { get; set; } + public string? City { get; set; } + public string? PostalCode { get; set; } + public string? Country { get; set; } + public string? FacebookUrl { get; set; } + public string? InstagramUrl { get; set; } + public string? TwitterUrl { get; set; } + } +} \ No newline at end of file diff --git a/Webshop.Application/Services/Admin/AdminShopInfoService.cs b/Webshop.Application/Services/Admin/AdminShopInfoService.cs new file mode 100644 index 0000000..d744a09 --- /dev/null +++ b/Webshop.Application/Services/Admin/AdminShopInfoService.cs @@ -0,0 +1,74 @@ +// src/Webshop.Application/Services/Admin/AdminShopInfoService.cs +using System; +using System.Threading.Tasks; +using Webshop.Application.DTOs.Shop; +using Webshop.Domain.Entities; +using Webshop.Domain.Interfaces; + +namespace Webshop.Application.Services.Admin +{ + public class AdminShopInfoService : IAdminShopInfoService + { + private readonly IShopInfoRepository _shopInfoRepository; + + public AdminShopInfoService(IShopInfoRepository shopInfoRepository) + { + _shopInfoRepository = shopInfoRepository; + } + + public async Task GetShopInfoAsync() + { + var shopInfo = await _shopInfoRepository.GetAsync(); + + if (shopInfo == null) + { + // Erstelle einen Standardeintrag, falls keiner existiert + shopInfo = new ShopInfo { Id = Guid.NewGuid(), ShopName = "Mein Webshop", ContactEmail = "admin@example.com" }; + await _shopInfoRepository.AddAsync(shopInfo); + } + + return new AdminShopInfoDto + { + ShopName = shopInfo.ShopName, + Slogan = shopInfo.Slogan, + ContactEmail = shopInfo.ContactEmail, + PhoneNumber = shopInfo.PhoneNumber, + Street = shopInfo.Street, + City = shopInfo.City, + PostalCode = shopInfo.PostalCode, + Country = shopInfo.Country, + VatNumber = shopInfo.VatNumber, + CompanyRegistrationNumber = shopInfo.CompanyRegistrationNumber, + FacebookUrl = shopInfo.FacebookUrl, + InstagramUrl = shopInfo.InstagramUrl, + TwitterUrl = shopInfo.TwitterUrl + }; + } + + public async Task UpdateShopInfoAsync(AdminShopInfoDto shopInfoDto) + { + var shopInfo = await _shopInfoRepository.GetAsync(); + if (shopInfo == null) + { + throw new InvalidOperationException("ShopInfo entity does not exist and cannot be updated."); + } + + // Mappe die Werte vom DTO auf die Entität + shopInfo.ShopName = shopInfoDto.ShopName; + shopInfo.Slogan = shopInfoDto.Slogan; + shopInfo.ContactEmail = shopInfoDto.ContactEmail; + shopInfo.PhoneNumber = shopInfoDto.PhoneNumber; + shopInfo.Street = shopInfoDto.Street; + shopInfo.City = shopInfoDto.City; + shopInfo.PostalCode = shopInfoDto.PostalCode; + shopInfo.Country = shopInfoDto.Country; + shopInfo.VatNumber = shopInfoDto.VatNumber; + shopInfo.CompanyRegistrationNumber = shopInfoDto.CompanyRegistrationNumber; + shopInfo.FacebookUrl = shopInfoDto.FacebookUrl; + shopInfo.InstagramUrl = shopInfoDto.InstagramUrl; + shopInfo.TwitterUrl = shopInfoDto.TwitterUrl; + + await _shopInfoRepository.UpdateAsync(shopInfo); + } + } +} \ No newline at end of file diff --git a/Webshop.Application/Services/Admin/Interfaces/IAdminShopInfoService.cs b/Webshop.Application/Services/Admin/Interfaces/IAdminShopInfoService.cs new file mode 100644 index 0000000..eb79df3 --- /dev/null +++ b/Webshop.Application/Services/Admin/Interfaces/IAdminShopInfoService.cs @@ -0,0 +1,12 @@ +// src/Webshop.Application/Services/Admin/IAdminShopInfoService.cs +using System.Threading.Tasks; +using Webshop.Application.DTOs.Shop; + +namespace Webshop.Application.Services.Admin +{ + public interface IAdminShopInfoService + { + Task GetShopInfoAsync(); + Task UpdateShopInfoAsync(AdminShopInfoDto shopInfoDto); + } +} \ No newline at end of file diff --git a/Webshop.Application/Services/Public/Interfaces/IShopInfoService.cs b/Webshop.Application/Services/Public/Interfaces/IShopInfoService.cs new file mode 100644 index 0000000..4d0aa24 --- /dev/null +++ b/Webshop.Application/Services/Public/Interfaces/IShopInfoService.cs @@ -0,0 +1,11 @@ +// src/Webshop.Application/Services/Public/IShopInfoService.cs +using System.Threading.Tasks; +using Webshop.Application.DTOs.Shop; + +namespace Webshop.Application.Services.Public +{ + public interface IShopInfoService + { + Task GetPublicShopInfoAsync(); + } +} \ No newline at end of file diff --git a/Webshop.Application/Services/Public/ShopInfoService.cs b/Webshop.Application/Services/Public/ShopInfoService.cs new file mode 100644 index 0000000..9c62225 --- /dev/null +++ b/Webshop.Application/Services/Public/ShopInfoService.cs @@ -0,0 +1,43 @@ +// src/Webshop.Application/Services/Public/ShopInfoService.cs +using System.Threading.Tasks; +using Webshop.Application.DTOs.Shop; +using Webshop.Domain.Interfaces; + +namespace Webshop.Application.Services.Public +{ + public class ShopInfoService : IShopInfoService + { + private readonly IShopInfoRepository _shopInfoRepository; + + public ShopInfoService(IShopInfoRepository shopInfoRepository) + { + _shopInfoRepository = shopInfoRepository; + } + + public async Task GetPublicShopInfoAsync() + { + var shopInfo = await _shopInfoRepository.GetAsync(); + + if (shopInfo == null) + { + // Gib Standardwerte zurück, wenn nichts konfiguriert ist + return new PublicShopInfoDto { ShopName = "Webshop" }; + } + + return new PublicShopInfoDto + { + ShopName = shopInfo.ShopName, + Slogan = shopInfo.Slogan, + ContactEmail = shopInfo.ContactEmail, + PhoneNumber = shopInfo.PhoneNumber, + Street = shopInfo.Street, + City = shopInfo.City, + PostalCode = shopInfo.PostalCode, + Country = shopInfo.Country, + FacebookUrl = shopInfo.FacebookUrl, + InstagramUrl = shopInfo.InstagramUrl, + TwitterUrl = shopInfo.TwitterUrl + }; + } + } +} \ No newline at end of file diff --git a/Webshop.Domain/Entities/ShopInfo.cs b/Webshop.Domain/Entities/ShopInfo.cs new file mode 100644 index 0000000..c774f32 --- /dev/null +++ b/Webshop.Domain/Entities/ShopInfo.cs @@ -0,0 +1,58 @@ +// src/Webshop.Domain/Entities/ShopInfo.cs +using System; +using System.ComponentModel.DataAnnotations; + +namespace Webshop.Domain.Entities +{ + /// + /// Enthält die zentralen Stammdaten des Shops. + /// Es sollte nur eine einzige Zeile in dieser Tabelle existieren. + /// + public class ShopInfo + { + [Key] + public Guid Id { get; set; } + + [Required, MaxLength(255)] + public string ShopName { get; set; } = string.Empty; + + [MaxLength(500)] + public string? Slogan { get; set; } // << NEU: Für Marketing >> + + [Required, MaxLength(255)] + public string ContactEmail { get; set; } = string.Empty; + + [MaxLength(50)] + public string? PhoneNumber { get; set; } + + // --- Firmenadresse --- + [MaxLength(255)] + public string? Street { get; set; } + + [MaxLength(100)] + public string? City { get; set; } + + [MaxLength(20)] + public string? PostalCode { get; set; } + + [MaxLength(100)] + public string? Country { get; set; } + + // --- Rechtliches --- + [MaxLength(50)] + public string? VatNumber { get; set; } // Umsatzsteuer-ID + + [MaxLength(100)] + public string? CompanyRegistrationNumber { get; set; } // Handelsregisternummer + + // --- Social Media & Links --- + [MaxLength(255)] + public string? FacebookUrl { get; set; } // << NEU >> + + [MaxLength(255)] + public string? InstagramUrl { get; set; } // << NEU >> + + [MaxLength(255)] + public string? TwitterUrl { get; set; } // << NEU >> + } +} \ No newline at end of file diff --git a/Webshop.Domain/Interfaces/IShopInfoRepository.cs b/Webshop.Domain/Interfaces/IShopInfoRepository.cs new file mode 100644 index 0000000..5a8e71c --- /dev/null +++ b/Webshop.Domain/Interfaces/IShopInfoRepository.cs @@ -0,0 +1,13 @@ +// src/Webshop.Domain/Interfaces/IShopInfoRepository.cs +using System.Threading.Tasks; +using Webshop.Domain.Entities; + +namespace Webshop.Domain.Interfaces +{ + public interface IShopInfoRepository + { + Task GetAsync(); // Ruft die (einzige) ShopInfo-Zeile ab + Task UpdateAsync(ShopInfo shopInfo); + Task AddAsync(ShopInfo shopInfo); + } +} \ No newline at end of file diff --git a/Webshop.Infrastructure/Data/ApplicationDbContext.cs b/Webshop.Infrastructure/Data/ApplicationDbContext.cs index e6f3ae0..c7a00b0 100644 --- a/Webshop.Infrastructure/Data/ApplicationDbContext.cs +++ b/Webshop.Infrastructure/Data/ApplicationDbContext.cs @@ -30,6 +30,7 @@ namespace Webshop.Infrastructure.Data public DbSet ProductDiscounts { get; set; } = default!; public DbSet categorieDiscounts { get; set; } = default!; public DbSet ProductImages { get; set; } = default!; + public DbSet ShopInfos { get; set; } = default!; protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/Webshop.Infrastructure/Repositories/ShopInfoRepository.cs b/Webshop.Infrastructure/Repositories/ShopInfoRepository.cs new file mode 100644 index 0000000..616b035 --- /dev/null +++ b/Webshop.Infrastructure/Repositories/ShopInfoRepository.cs @@ -0,0 +1,37 @@ +// src/Webshop.Infrastructure/Repositories/ShopInfoRepository.cs +using Microsoft.EntityFrameworkCore; +using System.Threading.Tasks; +using Webshop.Domain.Entities; +using Webshop.Domain.Interfaces; +using Webshop.Infrastructure.Data; + +namespace Webshop.Infrastructure.Repositories +{ + public class ShopInfoRepository : IShopInfoRepository + { + private readonly ApplicationDbContext _context; + + public ShopInfoRepository(ApplicationDbContext context) + { + _context = context; + } + + public async Task GetAsync() + { + // Es sollte immer nur eine Zeile geben, also nehmen wir die erste + return await _context.ShopInfos.FirstOrDefaultAsync(); + } + + public async Task UpdateAsync(ShopInfo shopInfo) + { + _context.ShopInfos.Update(shopInfo); + await _context.SaveChangesAsync(); + } + + public async Task AddAsync(ShopInfo shopInfo) + { + await _context.ShopInfos.AddAsync(shopInfo); + await _context.SaveChangesAsync(); + } + } +} \ No newline at end of file