This commit is contained in:
younes elhaddoury
2025-09-17 10:28:02 +02:00
parent 9c8fb9b205
commit bb13759af4
288 changed files with 102393 additions and 0 deletions

View File

@@ -0,0 +1,138 @@
using System.Security.Claims;
using LEA.Models;
using LEA.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
namespace LEA.Controllers;
public class AccountController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<AccountController> _logger;
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
ILogger<AccountController> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
[HttpGet]
[AllowAnonymous]
public IActionResult Register()
{
return View(new RegisterViewModel());
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var existingUser = await _userManager.FindByEmailAsync(model.Email);
if (existingUser != null)
{
ModelState.AddModelError(nameof(model.Email), "Diese E-Mail-Adresse wird bereits verwendet.");
return View(model);
}
var user = new ApplicationUser
{
FullName = model.FullName.Trim(),
Email = model.Email.Trim(),
UserName = model.Email.Trim(),
CreatedAt = DateTime.UtcNow
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _userManager.AddClaimAsync(user, new Claim("FullName", user.FullName));
await _signInManager.SignInAsync(user, isPersistent: true);
_logger.LogInformation("Neuer Benutzer {Email} wurde erstellt und angemeldet.", user.Email);
TempData["Success"] = "Registrierung erfolgreich. Willkommen zurück!";
return RedirectToAction("Index", "Applications");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, TranslateIdentityError(error));
}
return View(model);
}
[HttpGet]
[AllowAnonymous]
public IActionResult Login(string? returnUrl = null)
{
return View(new LoginViewModel { ReturnUrl = returnUrl });
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await _userManager.FindByEmailAsync(model.Email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Ungültige Anmeldedaten.");
return View(model);
}
var result = await _signInManager.PasswordSignInAsync(user, model.Password, model.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("Benutzer {Email} hat sich angemeldet.", user.Email);
if (!string.IsNullOrWhiteSpace(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl))
{
return Redirect(model.ReturnUrl);
}
TempData["Success"] = "Erfolgreich angemeldet.";
return RedirectToAction("Index", "Applications");
}
ModelState.AddModelError(string.Empty, "Ungültige Anmeldedaten.");
return View(model);
}
[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
TempData["Success"] = "Sie wurden abgemeldet.";
return RedirectToAction("Index", "Home");
}
private static string TranslateIdentityError(IdentityError error) => error.Code switch
{
"PasswordTooShort" => "Das Passwort ist zu kurz.",
"PasswordRequiresNonAlphanumeric" => "Das Passwort muss mindestens ein Sonderzeichen enthalten.",
"PasswordRequiresDigit" => "Das Passwort muss mindestens eine Zahl enthalten.",
"PasswordRequiresUpper" => "Das Passwort muss mindestens einen Großbuchstaben enthalten.",
"PasswordRequiresLower" => "Das Passwort muss mindestens einen Kleinbuchstaben enthalten.",
"DuplicateEmail" or "DuplicateUserName" => "Diese E-Mail-Adresse wird bereits verwendet.",
"InvalidEmail" => "Bitte eine gültige E-Mail-Adresse eingeben.",
_ => error.Description
};
}

View File

@@ -0,0 +1,289 @@
using LEA.Data;
using LEA.Extensions;
using LEA.Models;
using LEA.ViewModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
namespace LEA.Controllers;
[Authorize]
public class ApplicationsController : Controller
{
private readonly AppDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
public ApplicationsController(AppDbContext context, UserManager<ApplicationUser> userManager)
{
_context = context;
_userManager = userManager;
}
public async Task<IActionResult> Index(string? searchTerm, ApplicationStatus? status)
{
var userId = GetCurrentUserId();
var query = _context.Applications
.Include(a => a.Contact)
.Where(a => a.UserId == userId);
if (!string.IsNullOrWhiteSpace(searchTerm))
{
var term = $"%{searchTerm.Trim()}%";
query = query.Where(a =>
EF.Functions.Like(a.Company, term) ||
EF.Functions.Like(a.Role, term) ||
EF.Functions.Like(a.Source ?? string.Empty, term) ||
EF.Functions.Like(a.Notes ?? string.Empty, term) ||
(a.Contact != null && (
EF.Functions.Like(a.Contact.FullName ?? string.Empty, term) ||
EF.Functions.Like(a.Contact.Email ?? string.Empty, term))));
}
if (status.HasValue)
{
query = query.Where(a => a.Status == status.Value);
}
var applications = await query
.OrderByDescending(a => a.UpdatedAt)
.ThenByDescending(a => a.AppliedOn)
.ToListAsync();
var statsQuery = _context.Applications.Where(a => a.UserId == userId);
var statusCounts = await statsQuery
.GroupBy(a => a.Status)
.Select(g => new { g.Key, Count = g.Count() })
.ToListAsync();
var statusDictionary = Enum.GetValues<ApplicationStatus>()
.ToDictionary(s => s, s => statusCounts.FirstOrDefault(sc => sc.Key == s)?.Count ?? 0);
var model = new ApplicationListViewModel
{
Applications = applications,
SearchTerm = searchTerm,
StatusFilter = status,
StatusOptions = GetStatusSelectList(status, includeAllOption: true),
StatusCounts = statusDictionary,
TotalApplications = await statsQuery.CountAsync()
};
return View(model);
}
[HttpGet]
public IActionResult Create()
{
var model = new ApplicationFormViewModel
{
AppliedOn = DateTime.Today,
Status = ApplicationStatus.Applied,
StatusOptions = GetStatusSelectList(ApplicationStatus.Applied),
Heading = "Bewerbung hinzufügen",
SubmitText = "Speichern"
};
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(ApplicationFormViewModel model)
{
if (!ModelState.IsValid)
{
model.StatusOptions = GetStatusSelectList(model.Status);
model.Heading = "Bewerbung hinzufügen";
model.SubmitText = "Speichern";
return View(model);
}
var application = new Application
{
Role = model.Role.Trim(),
Company = model.Company.Trim(),
Source = string.IsNullOrWhiteSpace(model.Source) ? null : model.Source.Trim(),
Status = model.Status,
AppliedOn = model.AppliedOn,
Notes = string.IsNullOrWhiteSpace(model.Notes) ? null : model.Notes.Trim(),
UserId = GetCurrentUserId(),
UpdatedAt = DateTime.UtcNow
};
if (HasContactInformation(model))
{
application.Contact = new Contact
{
FullName = string.IsNullOrWhiteSpace(model.ContactName) ? null : model.ContactName.Trim(),
Email = string.IsNullOrWhiteSpace(model.ContactEmail) ? null : model.ContactEmail.Trim(),
Phone = string.IsNullOrWhiteSpace(model.ContactPhone) ? null : model.ContactPhone.Trim()
};
}
_context.Applications.Add(application);
await _context.SaveChangesAsync();
TempData["Success"] = "Die Bewerbung wurde gespeichert.";
return RedirectToAction(nameof(Index));
}
[HttpGet]
public async Task<IActionResult> Edit(int id)
{
var application = await FindApplicationForCurrentUserAsync(id);
if (application == null)
{
return NotFound();
}
var model = new ApplicationFormViewModel
{
Id = application.Id,
Role = application.Role,
Company = application.Company,
Source = application.Source,
Status = application.Status,
AppliedOn = application.AppliedOn,
Notes = application.Notes,
ContactName = application.Contact?.FullName,
ContactEmail = application.Contact?.Email,
ContactPhone = application.Contact?.Phone,
StatusOptions = GetStatusSelectList(application.Status),
Heading = "Bewerbung bearbeiten",
SubmitText = "Änderungen speichern"
};
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, ApplicationFormViewModel model)
{
if (id != model.Id)
{
return NotFound();
}
if (!ModelState.IsValid)
{
model.StatusOptions = GetStatusSelectList(model.Status);
model.Heading = "Bewerbung bearbeiten";
model.SubmitText = "Änderungen speichern";
return View(model);
}
var application = await FindApplicationForCurrentUserAsync(id);
if (application == null)
{
return NotFound();
}
application.Role = model.Role.Trim();
application.Company = model.Company.Trim();
application.Source = string.IsNullOrWhiteSpace(model.Source) ? null : model.Source.Trim();
application.Status = model.Status;
application.AppliedOn = model.AppliedOn;
application.Notes = string.IsNullOrWhiteSpace(model.Notes) ? null : model.Notes.Trim();
application.UpdatedAt = DateTime.UtcNow;
if (HasContactInformation(model))
{
if (application.Contact == null)
{
application.Contact = new Contact();
}
application.Contact.FullName = string.IsNullOrWhiteSpace(model.ContactName) ? null : model.ContactName.Trim();
application.Contact.Email = string.IsNullOrWhiteSpace(model.ContactEmail) ? null : model.ContactEmail.Trim();
application.Contact.Phone = string.IsNullOrWhiteSpace(model.ContactPhone) ? null : model.ContactPhone.Trim();
}
else if (application.Contact != null)
{
_context.Contacts.Remove(application.Contact);
}
await _context.SaveChangesAsync();
TempData["Success"] = "Die Bewerbung wurde aktualisiert.";
return RedirectToAction(nameof(Index));
}
[HttpGet]
public async Task<IActionResult> Details(int id)
{
var application = await FindApplicationForCurrentUserAsync(id);
if (application == null)
{
return NotFound();
}
return View(application);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Delete(int id)
{
var application = await FindApplicationForCurrentUserAsync(id);
if (application == null)
{
return NotFound();
}
_context.Applications.Remove(application);
await _context.SaveChangesAsync();
TempData["Success"] = "Die Bewerbung wurde gelöscht.";
return RedirectToAction(nameof(Index));
}
private async Task<Application?> FindApplicationForCurrentUserAsync(int id)
{
var userId = GetCurrentUserId();
return await _context.Applications
.Include(a => a.Contact)
.Where(a => a.UserId == userId)
.FirstOrDefaultAsync(a => a.Id == id);
}
private IEnumerable<SelectListItem> GetStatusSelectList(ApplicationStatus? selectedStatus, bool includeAllOption = false)
{
var options = Enum.GetValues<ApplicationStatus>()
.Select(status => new SelectListItem
{
Text = status.GetDisplayName(),
Value = status.ToString(),
Selected = selectedStatus.HasValue && selectedStatus.Value == status
})
.ToList();
if (includeAllOption)
{
options.Insert(0, new SelectListItem
{
Text = "Alle Status",
Value = string.Empty,
Selected = !selectedStatus.HasValue
});
}
return options;
}
private bool HasContactInformation(ApplicationFormViewModel model)
{
return !string.IsNullOrWhiteSpace(model.ContactName)
|| !string.IsNullOrWhiteSpace(model.ContactEmail)
|| !string.IsNullOrWhiteSpace(model.ContactPhone);
}
private string GetCurrentUserId()
{
return _userManager.GetUserId(User)
?? throw new InvalidOperationException("Benutzer ist nicht angemeldet.");
}
}

View File

@@ -0,0 +1,97 @@
using System.Diagnostics;
using System.Globalization;
using LEA.Data;
using LEA.Models;
using LEA.ViewModels;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace LEA.Controllers;
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly AppDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;
public HomeController(
ILogger<HomeController> logger,
AppDbContext context,
UserManager<ApplicationUser> userManager)
{
_logger = logger;
_context = context;
_userManager = userManager;
}
public async Task<IActionResult> Index()
{
var viewModel = new DashboardViewModel();
if (User.Identity?.IsAuthenticated ?? false)
{
var userId = _userManager.GetUserId(User);
var user = await _userManager.GetUserAsync(User);
var applications = await _context.Applications
.Include(a => a.Contact)
.Where(a => a.UserId == userId)
.OrderByDescending(a => a.UpdatedAt)
.ThenByDescending(a => a.AppliedOn)
.Take(5)
.ToListAsync();
var statusCounts = await _context.Applications
.Where(a => a.UserId == userId)
.GroupBy(a => a.Status)
.Select(group => new { group.Key, Count = group.Count() })
.ToListAsync();
var startOfCurrentMonth = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
var startDate = startOfCurrentMonth.AddMonths(-5);
var monthlyResults = await _context.Applications
.Where(a => a.UserId == userId && a.AppliedOn >= startDate)
.GroupBy(a => new { a.AppliedOn.Year, a.AppliedOn.Month })
.Select(group => new { group.Key.Year, group.Key.Month, Count = group.Count() })
.ToListAsync();
var germanCulture = CultureInfo.GetCultureInfo("de-DE");
var monthlyStatistics = new List<MonthlyApplicationStat>();
for (var offset = 0; offset < 6; offset++)
{
var currentMonth = startDate.AddMonths(offset);
var match = monthlyResults.FirstOrDefault(result =>
result.Year == currentMonth.Year && result.Month == currentMonth.Month);
var label = currentMonth.ToString("MMM yyyy", germanCulture);
monthlyStatistics.Add(new MonthlyApplicationStat(label, match?.Count ?? 0));
}
viewModel.IsAuthenticated = true;
viewModel.FullName = user?.FullName;
viewModel.RecentApplications = applications;
viewModel.TotalApplications = statusCounts.Sum(sc => sc.Count);
viewModel.AppliedCount = statusCounts.FirstOrDefault(sc => sc.Key == ApplicationStatus.Applied)?.Count ?? 0;
viewModel.InterviewCount = statusCounts.FirstOrDefault(sc => sc.Key == ApplicationStatus.Interview)?.Count ?? 0;
viewModel.OfferCount = statusCounts.FirstOrDefault(sc => sc.Key == ApplicationStatus.Offer)?.Count ?? 0;
viewModel.RejectedCount = statusCounts.FirstOrDefault(sc => sc.Key == ApplicationStatus.Rejected)?.Count ?? 0;
viewModel.MonthlyApplications = monthlyStatistics;
}
return View(viewModel);
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}