From 3f4c0720e1e3421431e7baa20882a4a4512a7fab Mon Sep 17 00:00:00 2001 From: ivar Date: Sun, 19 Oct 2025 23:41:23 +0200 Subject: Initial --- src/Controllers/AccountController.cs | 97 +++++++++++++++++++++ src/Controllers/BaseController.cs | 30 +++++++ src/Controllers/CabinsController.cs | 93 ++++++++++++++++++++ src/Controllers/ReservationsController.cs | 137 ++++++++++++++++++++++++++++++ src/Controllers/UsersController.cs | 93 ++++++++++++++++++++ 5 files changed, 450 insertions(+) create mode 100644 src/Controllers/AccountController.cs create mode 100644 src/Controllers/BaseController.cs create mode 100644 src/Controllers/CabinsController.cs create mode 100644 src/Controllers/ReservationsController.cs create mode 100644 src/Controllers/UsersController.cs (limited to 'src/Controllers') diff --git a/src/Controllers/AccountController.cs b/src/Controllers/AccountController.cs new file mode 100644 index 0000000..e1f6946 --- /dev/null +++ b/src/Controllers/AccountController.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using IOL.Fagprove.Data; +using IOL.Fagprove.Data.DTOs; +using IOL.Fagprove.Services.Interfaces; +using IOL.Fagprove.Utilities; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace IOL.Fagprove.Controllers +{ + public class AccountController : BaseController + { + private readonly AppDbContext _context; + private readonly IUserService _userService; + + public AccountController(AppDbContext context, IUserService userService) + { + _context = context; + _userService = userService; + } + + [HttpPost("login")] + [ValidateAntiForgeryToken] + [AllowAnonymous] + public async Task Login(LoginDto data) + { + if (data.Password.IsMissing() || data.Username.IsMissing()) return BadRequest("Ett eller flere felt er ikke fylt inn."); + if (data.Username.IsEmail() == false) return BadRequest("Det ser ikke ut som en e-postadresse"); + var user = _context.Users.SingleOrDefault(u => u.Email == data.Username); + var passwordMatches = PasswordHasher.PasswordMatches(user?.Password ?? string.Empty, data.Password ?? string.Empty); + if (user == default || passwordMatches == false) return BadRequest("E-postadresse eller passord er feil"); + + var claims = new List + { + new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), + new Claim(ClaimTypes.Name, user.Name), + new Claim(ClaimTypes.Role, user.Role.ToString()) + }; + + var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + var authenticationProperties = new AuthenticationProperties + { + AllowRefresh = true, + IssuedUtc = DateTimeOffset.UtcNow + }; + + await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(claimsIdentity), authenticationProperties); + return Ok(); + } + + [HttpGet("logout")] + public async Task Logout() + { + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + return Redirect("/"); + } + + [AllowAnonymous] + [HttpPost("forgot")] + public ActionResult StartForgotPasswordFlow(string email) + { + var user = _context.Users.SingleOrDefault(u => u.Email == email); + if (user == default) return Ok(); + var task = _userService.SetNewTemporaryPasswordAndNotifyUser(user); + if (task) return Ok(); + return StatusCode(500); + } + + [HttpPut("password")] + public ActionResult SetPasswordAfterTemporary(UpdatePasswordDto data) + { + var user = _context.Users.SingleOrDefault(u => u.Id == LoggedInUser.Id); + if (user == default) + { + SignOut(); + return Unauthorized(new { error = "Vi fant deg ikke i våre systemer." }); + } + + if (data.Password.IsMissing() || data.Password.IsMissing()) + { + return BadRequest(new { error = "Ett eller flere felt er ikke fylt inn." }); + } + if (data.Password.Length <= 5) return BadRequest(new { error = "Passordet er ikke langt nok." }); + if (data.Password != data.PasswordOnceMore) return BadRequest(new { error = "Passordene er forksjellige" }); + var passwordIsUpdated = _userService.UpdatePassword(user, data.Password); + if (passwordIsUpdated) return Ok(); + return StatusCode(500, new { error = "Noe gikk galt, vennligst prøv igjen senere." }); + } + } +} \ No newline at end of file diff --git a/src/Controllers/BaseController.cs b/src/Controllers/BaseController.cs new file mode 100644 index 0000000..c90bfb4 --- /dev/null +++ b/src/Controllers/BaseController.cs @@ -0,0 +1,30 @@ +using System; +using System.Security.Claims; +using IOL.Fagprove.Data.Enums; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using IOL.Fagprove.Utilities; + +namespace IOL.Fagprove.Controllers +{ + [Authorize] + [Produces(("application/json"))] + [ApiController] + [Route("api/[controller]")] + public class BaseController : ControllerBase + { + public LoggedInUserModel LoggedInUser => new LoggedInUserModel + { + Id = User.GetClaimValue(ClaimTypes.NameIdentifier).ToGuid(), + Name = User.GetClaimValue(ClaimTypes.Name), + Role = User.GetClaimValue(ClaimTypes.Role).ToUserRole() + }; + + public class LoggedInUserModel + { + public Guid Id { get; set; } + public string Name { get; set; } + public UserRole Role { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Controllers/CabinsController.cs b/src/Controllers/CabinsController.cs new file mode 100644 index 0000000..4fe87cc --- /dev/null +++ b/src/Controllers/CabinsController.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection.Metadata; +using IOL.Fagprove.Data; +using IOL.Fagprove.Data.DTOs; +using IOL.Fagprove.Data.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using IOL.Fagprove.Utilities; + +namespace IOL.Fagprove.Controllers +{ + [Authorize("Administrator")] + public class CabinsController : BaseController + { + private readonly AppDbContext _context; + + public CabinsController(AppDbContext context) + { + _context = context; + } + + [HttpGet] + public ActionResult> GetCabins() + { + var cabins = _context.Cabins.ToList(); + var res = new List(); + foreach (var cabin in cabins) + { + var fieldName = StaticData.CabinFields.FirstOrDefault(s => s.Id == cabin.CategoryId)?.Name ?? "Ukjent"; + res.Add(new CabinDto + { + Capacity = cabin.Capacity, + Description = cabin.Description, + Id = cabin.Id, + Name = cabin.Name, + Price = cabin.Price, + CategoryId = cabin.CategoryId, + CategoryName = fieldName + }); + } + + return res; + } + + [HttpPost("create")] + public ActionResult CreateCabin(ReservationObject data) + { + if (data.Name.IsMissing()) return BadRequest(new {error = "Navn er påkrevd"}); + data.CreatedBy = LoggedInUser.Id; + data.CreatedUtc = DateTime.UtcNow; + _context.Cabins.Add(data); + _context.SaveChanges(); + return Ok(data.Name); + } + + [HttpPut("update")] + public ActionResult UpdateCabin(ReservationObject data) + { + if (data.Name.IsMissing()) return BadRequest("Navn er påkrevd"); + if (data.Id == Guid.Empty) return BadRequest(); + var cabinExists = _context.Cabins.Any(c => c.Id == data.Id); + if (!cabinExists) return BadRequest(new {error = "Fant ikke hytten"}); + data.ModifiedBy = LoggedInUser.Id; + data.ModifiedUtc = DateTime.UtcNow; + _context.Cabins.Update(data); + _context.Entry(data).Property(x => x.CreatedBy).IsModified = false; + _context.Entry(data).Property(x => x.CreatedUtc).IsModified = false; + _context.Entry(data).Property(x => x.Id).IsModified = false; + _context.SaveChanges(); + return Ok(); + } + + [HttpDelete("delete")] + public ActionResult DeleteCabin(CabinDto data) + { + var cabinToRemove = _context.Cabins.SingleOrDefault(c => c.Id == data.Id); + if (cabinToRemove == default) return Ok(); + _context.Cabins.Remove(cabinToRemove); + var existingFutureReservationsForThisCabin = _context.Reservations.Where(r => r.ReservationObjectId == cabinToRemove.Id + && DateTime.Compare(r.From, DateTime.Today) >= 0).ToList(); + if (existingFutureReservationsForThisCabin.Count != 0) + { + _context.Reservations.RemoveRange(existingFutureReservationsForThisCabin); + } + + _context.SaveChanges(); + return Ok(); + } + } +} \ No newline at end of file diff --git a/src/Controllers/ReservationsController.cs b/src/Controllers/ReservationsController.cs new file mode 100644 index 0000000..912f591 --- /dev/null +++ b/src/Controllers/ReservationsController.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Runtime.InteropServices.ComTypes; +using Flurl.Util; +using IOL.Fagprove.Data; +using IOL.Fagprove.Data.DTOs; +using IOL.Fagprove.Data.Enums; +using IOL.Fagprove.Data.Models; +using IOL.Fagprove.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Graph; +using IOL.Fagprove.Services; + +namespace IOL.Fagprove.Controllers +{ + public class ReservationsController : BaseController + { + private readonly AppDbContext _context; + private readonly IAppReservationService _reservationService; + + public ReservationsController(AppDbContext context, IAppReservationService reservationService) + { + _context = context; + _reservationService = reservationService; + } + // /api/reservations + [HttpGet] + public ActionResult> GetReservationsForUser() + { + return _context.Reservations.Where(r => r.UserId == LoggedInUser.Id).ToList(); + } + + [HttpGet("all")] + [Authorize("Administrator")] + public ActionResult> GetAllReservations() + { + return _context.Reservations.Select(r => new ReservationDto + { + Id = r.Id, + Cabin = _context.Cabins.SingleOrDefault(c => c.Id == r.ReservationObjectId).Name ?? "", + From = r.From.ToString("dd-MM-yyyy"), + To = r.To.ToString("dd-MM-yyyy"), + Status = r.Status, + Name = _context.Users.SingleOrDefault(u => u.Id == r.UserId).Name ?? "Ukjent bruker" + }).ToList(); + } + + [HttpGet("occupancy")] + public ActionResult> GetOccupany(Guid cabinId) + { + var today = DateTime.Today; + var reservationsForThisCabin = _context.Reservations.Where(r => r.ReservationObjectId == cabinId + && r.From > today && r.Status == ReservationStatus.Granted).ToList(); + if (reservationsForThisCabin.Count == 0) return default; + var occupiedDates = new List(); + foreach (var reservation in reservationsForThisCabin) + { + for (var dt = reservation.From; dt <= reservation.To; dt = dt.AddDays(1)) + { + occupiedDates.Add(dt.Date); + } + } + + return occupiedDates; + } + + [HttpPost("create")] + public ActionResult CreateReservation(Reservation data) + { + var reservationIsPossible = _reservationService.ReservationIsPossible(data); + if (!reservationIsPossible) return BadRequest(new {error = "Reservasjonen er ikke mulig lenger."}); + data.UserId = LoggedInUser.Id; + data.CreatedBy = LoggedInUser.Id; + data.CreatedUtc = DateTime.UtcNow; + data.Status = ReservationStatus.Pending; + _context.Reservations.Add(data); + _context.SaveChanges(); + return Ok(); + } + + [HttpGet("grant")] + [Authorize("Administrator")] + public ActionResult GrantReservation(Guid id) + { + if (id == Guid.Empty) return BadRequest(); + var reservation = _context.Reservations.SingleOrDefault(r => r.Id == id); + if (reservation == default) return BadRequest(new {error = "Fant ikke reservasjonen"}); + if (reservation.Status == ReservationStatus.Granted || reservation.Status == ReservationStatus.Expired) return BadRequest(new {error = "Denne reservasjonen er allerede godkjent"}); + reservation.Status = ReservationStatus.Granted; + reservation.ModifiedBy = LoggedInUser.Id; + reservation.ModifiedUtc = DateTime.UtcNow; + _context.Reservations.Update(reservation); + _context.Entry(reservation).Property(x => x.CreatedBy).IsModified = false; + _context.Entry(reservation).Property(x => x.CreatedUtc).IsModified = false; + _context.Entry(reservation).Property(x => x.Id).IsModified = false; + _context.SaveChanges(); + // TODO: Inform user if mail fails + var emailSent = _reservationService.SendReservationStatusMail(reservation); + return Ok(); + } + + [HttpGet("deny")] + [Authorize("Administrator")] + public ActionResult DenyReservation(Guid id) + { + if (id == Guid.Empty) return BadRequest(); + var reservation = _context.Reservations.SingleOrDefault(r => r.Id == id); + if (reservation == default) return BadRequest(new {error = "Fant ikke reservasjonen"}); + if (reservation.Status == ReservationStatus.Rejected || reservation.Status == ReservationStatus.Expired) return BadRequest(new {error = "Denne reservasjonen er allerede avvist"}); + reservation.Status = ReservationStatus.Rejected; + reservation.ModifiedBy = LoggedInUser.Id; + reservation.ModifiedUtc = DateTime.UtcNow; + _context.Reservations.Update(reservation); + _context.Entry(reservation).Property(x => x.CreatedBy).IsModified = false; + _context.Entry(reservation).Property(x => x.CreatedUtc).IsModified = false; + _context.Entry(reservation).Property(x => x.Id).IsModified = false; + _context.SaveChanges(); + // TODO: Inform user if mail fails + var emailSent = _reservationService.SendReservationStatusMail(reservation); + return Ok(); + } + + [HttpDelete("delete")] + public ActionResult DeleteReservation(Guid reservationId) + { + var reservation = _context.Reservations.SingleOrDefault(r => r.Id == reservationId); + if (reservation == default) return BadRequest(new {error = "Fant ikke reservasjonen"}); + if (reservation.UserId != LoggedInUser.Id) return Unauthorized(new {error = "Du har ikke lov til å slette denne reservasjonen"}); + _context.Reservations.Remove(reservation); + _context.SaveChanges(); + return Ok(); + } + } +} \ No newline at end of file diff --git a/src/Controllers/UsersController.cs b/src/Controllers/UsersController.cs new file mode 100644 index 0000000..0d3211b --- /dev/null +++ b/src/Controllers/UsersController.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using IOL.Fagprove.Data; +using IOL.Fagprove.Data.DTOs; +using IOL.Fagprove.Data.Models; +using IOL.Fagprove.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace IOL.Fagprove.Controllers +{ + [Authorize("Administrator")] + public class UsersController : BaseController + { + private readonly AppDbContext _context; + private readonly IUserService _userService; + + public UsersController(AppDbContext context, IUserService userService) + { + _context = context; + _userService = userService; + } + + [HttpGet] + public ActionResult> GetUsers() + { + return _context.Users.Select(u => new UserDto + { + Email = u.Email, + Id = u.Id, + Name = u.Name, + Role = u.Role + }).ToList(); + } + + [HttpPost("create")] + public ActionResult CreateUser(UserDto payload) + { + var userExists = _context.Users.Any(u => u.Email == payload.Email); + if (userExists) + return BadRequest(new {error = "En bruker med den e-postadressen finnes allerede"}); + var newUser = new User + { + Email = payload.Email, + Name = payload.Name, + Role = payload.Role, + CreatedBy = LoggedInUser.Id, + CreatedUtc = DateTime.UtcNow, + Id = Guid.NewGuid() + }; + _context.Users.Add(newUser); + _context.SaveChanges(); + var welcomeTask = _userService.SetTemporaryPasswordAndSendWelcomeMail(newUser); + if (!welcomeTask) + return StatusCode(500, new {error = "En feil oppstod, prøv igjen senere"}); + return Ok(payload); + } + + [HttpDelete("delete")] + public ActionResult DeleteUser(UserDto payload) + { + var userToDelete = _context.Users.SingleOrDefault(u => u.Id == payload.Id); + if (userToDelete == default) return BadRequest(new {error = "Fant ikke brukeren"}); + _context.Users.Remove(userToDelete); + _context.SaveChanges(); + return Ok(userToDelete.Name); + } + + [HttpPut("update")] + public ActionResult UpdateUser(UserDto payload) + { + var user = _context.Users.SingleOrDefault(u => u.Id == payload.Id); + if (user == default) return BadRequest(new {error = "Fant ikke brukeren"}); + if (user.Email != payload.Email) + { + var userExists = _context.Users.Any(u => u.Email == payload.Email); + if (userExists) return BadRequest(new {error = "En bruker med den e-postadressen finnes allerede"}); + } + user.ModifiedBy = LoggedInUser.Id; + user.ModifiedUtc = DateTime.UtcNow; + user.Email = payload.Email; + user.Name = payload.Name; + user.Role = payload.Role; + _context.Users.Update(user); + _context.Entry(user).Property(x => x.CreatedBy).IsModified = false; + _context.Entry(user).Property(x => x.CreatedUtc).IsModified = false; + _context.Entry(user).Property(x => x.Id).IsModified = false; + _context.SaveChanges(); + return Ok(); + } + } +} \ No newline at end of file -- cgit v1.3