From 4388d442c6ed29d069b6196711fa7c091956cd22 Mon Sep 17 00:00:00 2001 From: fixterjake Date: Tue, 17 Jun 2025 11:13:29 -0400 Subject: [PATCH 1/2] lots of updates --- Memphis.API/Controllers/AirportsController.cs | 14 +- Memphis.API/Controllers/CommentsController.cs | 69 ++--- .../Controllers/EmailLogsController.cs | 4 +- .../Controllers/EventPositionsController.cs | 10 +- .../EventRegistrationController.cs | 12 +- Memphis.API/Controllers/EventsController.cs | 16 +- .../Controllers/ExamRequestsController.cs | 261 ++++++++++++++++++ Memphis.API/Controllers/FeedbackController.cs | 16 +- Memphis.API/Controllers/FilesController.cs | 10 +- Memphis.API/Controllers/NewsController.cs | 12 +- Memphis.API/Controllers/OtsController.cs | 14 +- Memphis.API/Controllers/ProfileController.cs | 67 +++++ Memphis.API/Controllers/SessionsController.cs | 7 +- .../Controllers/StaffingRequestsController.cs | 36 +++ .../TrainingMilestonesController.cs | 8 +- .../TrainingSchedulesController.cs | 4 +- .../Controllers/TransferRequestsController.cs | 2 +- Memphis.API/Controllers/UsersController.cs | 259 ++++++++++++++++- .../Extensions/HttpContextExtensions.cs | 4 +- Memphis.API/Program.cs | 1 + .../Validators/ExamRequestValidator.cs | 14 + Memphis.Shared/Dtos/ExamRequestDto.cs | 34 +++ Memphis.Shared/Models/Comment.cs | 2 - Memphis.Shared/Models/ExamRequest.cs | 7 + Memphis.Shared/Models/StaffingRequest.cs | 1 + Memphis.Shared/Utils/Constants.cs | 162 +++-------- 26 files changed, 798 insertions(+), 248 deletions(-) create mode 100644 Memphis.API/Controllers/ExamRequestsController.cs create mode 100644 Memphis.API/Controllers/ProfileController.cs create mode 100644 Memphis.API/Validators/ExamRequestValidator.cs create mode 100644 Memphis.Shared/Dtos/ExamRequestDto.cs diff --git a/Memphis.API/Controllers/AirportsController.cs b/Memphis.API/Controllers/AirportsController.cs index e12042b..24968b9 100644 --- a/Memphis.API/Controllers/AirportsController.cs +++ b/Memphis.API/Controllers/AirportsController.cs @@ -38,7 +38,7 @@ public AirportsController(DatabaseContext context, RedisService redisService, Lo [HttpPost] - [Authorize(Roles = Constants.CanAirports)] + [Authorize(Roles = Constants.FacilitiesStaff)] [ProducesResponseType(typeof(Response), 201)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -48,7 +48,7 @@ public async Task>> CreateAirport(AirportPayload { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanAirportsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FacilitiesStaffList)) { return StatusCode(401); } @@ -70,7 +70,7 @@ public async Task>> CreateAirport(AirportPayload Icao = payload.Icao.ToUpper() }); await _context.SaveChangesAsync(); - string newData = JsonConvert.SerializeObject(result.Entity); + var newData = JsonConvert.SerializeObject(result.Entity); await _loggingService.AddWebsiteLog(Request, $"Created airport {result.Entity.Id}", string.Empty, newData); return StatusCode(201, new Response @@ -143,7 +143,7 @@ public async Task>>> GetAirport(int airport } [HttpPut("{airportId:int}")] - [Authorize(Roles = Constants.CanAirports)] + [Authorize(Roles = Constants.FacilitiesStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -154,7 +154,7 @@ public async Task>> UpdateAirport(int airportId, { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanAirportsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FacilitiesStaffList)) { return StatusCode(401); } @@ -204,7 +204,7 @@ public async Task>> UpdateAirport(int airportId, } [HttpDelete("{airportId:int}")] - [Authorize(Roles = Constants.CanAirports)] + [Authorize(Roles = Constants.FacilitiesStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(401)] [ProducesResponseType(403)] @@ -214,7 +214,7 @@ public async Task>> DeleteAirport(int airportId) { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanAirportsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FacilitiesStaffList)) { return StatusCode(401); } diff --git a/Memphis.API/Controllers/CommentsController.cs b/Memphis.API/Controllers/CommentsController.cs index 6616dd0..2719150 100644 --- a/Memphis.API/Controllers/CommentsController.cs +++ b/Memphis.API/Controllers/CommentsController.cs @@ -37,7 +37,7 @@ public CommentsController(DatabaseContext context, RedisService redisService, Lo } [HttpPost] - [Authorize(Roles = Constants.CanComment)] + [Authorize(Roles = Constants.AllStaff)] [ProducesResponseType(typeof(Response), 201)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -48,13 +48,7 @@ public async Task>> CreateComment(CommentPayload { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanCommentList)) - { - return StatusCode(401); - } - - // Check if they can add a confidential comment - if (payload.Confidential && !await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanCommentConfidentialList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.AllStaffList)) { return StatusCode(401); } @@ -96,7 +90,6 @@ public async Task>> CreateComment(CommentPayload { User = user, Submitter = submitter, - Confidential = payload.Confidential, Message = payload.Message, }); await _context.SaveChangesAsync(); @@ -119,7 +112,7 @@ public async Task>> CreateComment(CommentPayload } [HttpGet("{userId:int}")] - [Authorize(Roles = $"{Constants.CanComment},{Constants.CanCommentConfidential}")] + [Authorize(Roles = Constants.AllStaff)] [ProducesResponseType(typeof(ResponsePaging>), 200)] [ProducesResponseType(typeof(Response), 400)] [ProducesResponseType(401)] @@ -159,48 +152,22 @@ public async Task>>> GetComments(int userId }); } - if (await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanCommentConfidentialList)) + var result = await _context.Comments + .Where(x => x.User == user) + .OrderBy(x => x.Timestamp) + .Skip((page - 1) * size).Take(size) + .ToListAsync(); + var totalCount = await _context.Comments + .Where(x => x.User == user) + .OrderBy(x => x.Timestamp).CountAsync(); + return Ok(new ResponsePaging> { - var confidentialResult = await _context.Comments - .Where(x => x.User == user) - .OrderBy(x => x.Timestamp) - .Skip((page - 1) * size).Take(size) - .ToListAsync(); - var confidentialTotalCount = await _context.Comments - .Where(x => x.User == user).CountAsync(); - return Ok(new ResponsePaging> - { - StatusCode = 200, - ResultCount = confidentialResult.Count, - TotalCount = confidentialTotalCount, - Message = $"Got {confidentialResult.Count} comments", - Data = confidentialResult - }); - } - - if (await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanCommentList)) - { - var result = await _context.Comments - .Where(x => x.User == user) - .Where(x => !x.Confidential) - .OrderBy(x => x.Timestamp) - .Skip((page - 1) * size).Take(size) - .ToListAsync(); - var totalCount = await _context.Comments - .Where(x => x.User == user) - .Where(x => !x.Confidential) - .OrderBy(x => x.Timestamp).CountAsync(); - return Ok(new ResponsePaging> - { - StatusCode = 200, - ResultCount = result.Count, - TotalCount = totalCount, - Message = $"Got {result.Count} comments", - Data = result - }); - } - - return StatusCode(401); + StatusCode = 200, + ResultCount = result.Count, + TotalCount = totalCount, + Message = $"Got {result.Count} comments", + Data = result + }); } catch (Exception ex) { diff --git a/Memphis.API/Controllers/EmailLogsController.cs b/Memphis.API/Controllers/EmailLogsController.cs index 373945d..05480ed 100644 --- a/Memphis.API/Controllers/EmailLogsController.cs +++ b/Memphis.API/Controllers/EmailLogsController.cs @@ -30,14 +30,14 @@ public EmailLogsController(DatabaseContext context, RedisService redisService, I } [HttpGet] - [Authorize(Roles = Constants.CanEmailLogs)] + [Authorize(Roles = Constants.SeniorStaff)] [ProducesResponseType(typeof(Response>), 200)] [ProducesResponseType(typeof(Response), 500)] public async Task>>> GetEmailLogs(int page, int size, string? to = null) { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanEmailLogsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorStaffList)) { return StatusCode(401); } diff --git a/Memphis.API/Controllers/EventPositionsController.cs b/Memphis.API/Controllers/EventPositionsController.cs index 7b685d4..76fb3f8 100644 --- a/Memphis.API/Controllers/EventPositionsController.cs +++ b/Memphis.API/Controllers/EventPositionsController.cs @@ -37,7 +37,7 @@ public EventPositionsController(DatabaseContext context, RedisService redisServi } [HttpPost] - [Authorize(Roles = Constants.CanEvents)] + [Authorize(Roles = Constants.EventsStaff)] [ProducesResponseType(typeof(Response), 201)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -48,7 +48,7 @@ public async Task>> CreateEventPosition(Eve { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanEventsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.EventsStaffList)) { return StatusCode(401); } @@ -119,7 +119,7 @@ public async Task>>> GetEventPosition }); } - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.AllStaffList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FullStaffList)) { if (!@event.IsOpen) { @@ -157,7 +157,7 @@ public async Task>>> GetEventPosition } [HttpDelete("Positions/{eventPositionId:int}")] - [Authorize(Roles = Constants.CanEvents)] + [Authorize(Roles = Constants.EventsStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(401)] [ProducesResponseType(403)] @@ -167,7 +167,7 @@ public async Task>>> GetEventPosition { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanEventsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.EventsStaffList)) { return StatusCode(401); } diff --git a/Memphis.API/Controllers/EventRegistrationController.cs b/Memphis.API/Controllers/EventRegistrationController.cs index 0f55760..99b8a5d 100644 --- a/Memphis.API/Controllers/EventRegistrationController.cs +++ b/Memphis.API/Controllers/EventRegistrationController.cs @@ -272,7 +272,7 @@ public async Task>> GetOwnEventRegistra } [HttpGet("Registrations/{eventId:int}")] - [Authorize(Roles = Constants.CanEvents)] + [Authorize(Roles = Constants.EventsStaff)] [ProducesResponseType(401)] [ProducesResponseType(403)] [ProducesResponseType(typeof(Response>), 200)] @@ -282,7 +282,7 @@ public async Task>>> GetEventRegi { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanEventsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.EventsStaffList)) { return StatusCode(401); } @@ -313,7 +313,7 @@ public async Task>>> GetEventRegi } [HttpPut("assign/{eventRegistrationId:int}")] - [Authorize(Roles = Constants.CanEvents)] + [Authorize(Roles = Constants.EventsStaff)] [ProducesResponseType(401)] [ProducesResponseType(403)] [ProducesResponseType(typeof(Response), 200)] @@ -324,7 +324,7 @@ public async Task>> AssignEventRegistra { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanEventsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.EventsStaffList)) { return StatusCode(401); } @@ -455,7 +455,7 @@ public async Task>> DeleteOwnEventRegistration(int } [HttpDelete("{eventRegistrationId:int}")] - [Authorize(Roles = Constants.CanEvents)] + [Authorize(Roles = Constants.EventsStaff)] [ProducesResponseType(401)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(typeof(Response), 404)] @@ -464,7 +464,7 @@ public async Task>> DeleteEventRegistration(int ev { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanEventsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.EventsStaffList)) { return StatusCode(401); } diff --git a/Memphis.API/Controllers/EventsController.cs b/Memphis.API/Controllers/EventsController.cs index ff37731..dcc3979 100644 --- a/Memphis.API/Controllers/EventsController.cs +++ b/Memphis.API/Controllers/EventsController.cs @@ -39,7 +39,7 @@ public EventsController(DatabaseContext context, RedisService redisService, Logg } [HttpPost] - [Authorize(Roles = Constants.CanEvents)] + [Authorize(Roles = Constants.EventsStaff)] [ProducesResponseType(typeof(Response), 201)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -49,7 +49,7 @@ public async Task>> CreateEvent(EventPayload payloa { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanEventsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.EventsStaffList)) { return StatusCode(401); } @@ -119,7 +119,7 @@ public async Task>> GetEvents(int page = 1, int siz { try { - var getClosed = await _redisService.ValidateRoles(Request.HttpContext.User, Constants.AllStaffList); + var getClosed = await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FullStaffList); if (getClosed) { var result = await _context.Events @@ -169,7 +169,7 @@ public async Task>> GetEvent(int eventId) { try { - var getClosed = await _redisService.ValidateRoles(Request.HttpContext.User, Constants.AllStaffList); + var getClosed = await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FullStaffList); var result = await _context.Events.FindAsync(eventId); if (result == null) { @@ -204,7 +204,7 @@ public async Task>> GetEvent(int eventId) } [HttpPut("{eventId:int}")] - [Authorize(Roles = Constants.CanEvents)] + [Authorize(Roles = Constants.EventsStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -215,7 +215,7 @@ public async Task>> UpdateEvent(int eventId, EventP { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanEventsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.EventsStaffList)) { return StatusCode(401); } @@ -282,7 +282,7 @@ public async Task>> UpdateEvent(int eventId, EventP [HttpDelete("{eventId:int}")] - [Authorize(Roles = Constants.CanEvents)] + [Authorize(Roles = Constants.EventsStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(401)] [ProducesResponseType(403)] @@ -292,7 +292,7 @@ public async Task>> UpdateEvent(int eventId, EventP { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanEventsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.EventsStaffList)) { return StatusCode(401); } diff --git a/Memphis.API/Controllers/ExamRequestsController.cs b/Memphis.API/Controllers/ExamRequestsController.cs new file mode 100644 index 0000000..ae9942e --- /dev/null +++ b/Memphis.API/Controllers/ExamRequestsController.cs @@ -0,0 +1,261 @@ +using FluentValidation; +using FluentValidation.Results; +using Memphis.API.Extensions; +using Memphis.API.Services; +using Memphis.Shared.Data; +using Memphis.Shared.Dtos; +using Memphis.Shared.Enums; +using Memphis.Shared.Models; +using Memphis.Shared.Utils; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; + +namespace Memphis.API.Controllers; + +[ApiController] +[Route("[controller]")] +[Produces("application/json")] +public class ExamRequestsController : ControllerBase +{ + + private readonly DatabaseContext _context; + private readonly RedisService _redisService; + private readonly LoggingService _loggingService; + private readonly IValidator _validator; + private readonly ISentryClient _sentryHub; + private readonly ILogger _logger; + + public ExamRequestsController(DatabaseContext context, RedisService redisService, LoggingService loggingService, + IValidator validator, ISentryClient sentryHub, ILogger logger) + { + _context = context; + _redisService = redisService; + _loggingService = loggingService; + _validator = validator; + _sentryHub = sentryHub; + _logger = logger; + } + + [HttpPost] + [Authorize(Roles = Constants.SeniorTrainingStaff)] + [ProducesResponseType(typeof(Response), 201)] + [ProducesResponseType(typeof(Response>), 400)] + [ProducesResponseType(401)] + [ProducesResponseType(403)] + [ProducesResponseType(typeof(Response), 500)] + public async Task>> CreateExamRequest(ExamRequestPayload payload) + { + try + { + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorTrainingStaffList)) + { + return StatusCode(401); + } + + var validation = await _validator.ValidateAsync(payload); + if (!validation.IsValid) + { + return BadRequest(new Response> + { + StatusCode = 400, + Message = "Validation failure", + Data = validation.Errors + }); + } + + var instructor = await _context.Users + .FirstOrDefaultAsync(x => x.Id == Request.HttpContext.GetCid()); + if (instructor == null) + { + return NotFound(new Response + { + StatusCode = 404, + Message = "Instructor not found", + Data = null + }); + } + + var student = await _context.Users + .FirstOrDefaultAsync(x => x.Id == payload.StudentId); + if (student == null) + { + return NotFound(new Response + { + StatusCode = 404, + Message = "Student not found", + Data = null + }); + } + + var exam = await _context.Exams + .FirstOrDefaultAsync(x => x.Id == payload.ExamId); + if (exam == null) + { + return NotFound(new Response + { + StatusCode = 404, + Message = "Exam not found", + Data = null + }); + } + + var result = await _context.ExamRequests.AddAsync(new ExamRequest + { + Instructor = instructor, + Student = student, + Exam = exam, + Reason = payload.Reason + }); + await _context.SaveChangesAsync(); + var newData = JsonConvert.SerializeObject(result.Entity); + await _loggingService.AddWebsiteLog(Request, $"Created ExamRequest {result.Entity.Id}", string.Empty, newData); + + return StatusCode(201, new Response + { + StatusCode = 201, + Message = $"Created exam request '{result.Entity.Id}'", + Data = ExamRequestDto.Parse(result.Entity) + }); + } + catch (Exception ex) + { + _logger.LogError("CreateExamRequest error '{Message}'\n{StackTrace}", ex.Message, ex.StackTrace); + return _sentryHub.CaptureException(ex).ReturnActionResult(); + } + } + + [HttpGet] + [Authorize(Roles = Constants.SeniorTrainingStaff)] + [ProducesResponseType(typeof(Response>), 200)] + [ProducesResponseType(401)] + [ProducesResponseType(403)] + [ProducesResponseType(typeof(Response), 500)] + public async Task>>> GetExamRequests(ExamRequestStatus status) + { + try + { + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorTrainingStaffList)) + { + return StatusCode(401); + } + + var result = await _context.ExamRequests + .Include(x => x.Instructor) + .Include(x => x.Student) + .Include(x => x.Exam) + .Where(x => x.Status == status) + .ToListAsync(); + + return Ok(new Response> + { + StatusCode = 200, + Message = $"Got {result.Count} exam requests", + Data = ExamRequestDto.ParseMany(result) + }); + } + catch (Exception ex) + { + _logger.LogError("GetExamRequests error '{Message}'\n{StackTrace}", ex.Message, ex.StackTrace); + return _sentryHub.CaptureException(ex).ReturnActionResult(); + } + } + + [HttpPut("{examRequestId:int}")] + [Authorize(Roles = Constants.SeniorTrainingStaff)] + [ProducesResponseType(typeof(Response>), 200)] + [ProducesResponseType(401)] + [ProducesResponseType(403)] + [ProducesResponseType(typeof(Response), 404)] + [ProducesResponseType(typeof(Response), 500)] + public async Task>> ProcessExamRequest(int examRequestId) + { + try + { + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorTrainingStaffList)) + { + return StatusCode(401); + } + + var examRequest = await _context.ExamRequests + .FirstOrDefaultAsync(x => x.Id == examRequestId); + if (examRequest == null) + { + return NotFound(new Response + { + StatusCode = 404, + Message = $"Exam request '{examRequestId}' not found", + Data = examRequestId + }); + } + + if (examRequest.Status != ExamRequestStatus.PENDING) + { + return BadRequest(new Response + { + StatusCode = 400, + Message = "Exam request is not pending", + Data = null + }); + } + + var oldData = JsonConvert.SerializeObject(examRequest); + examRequest.Status = ExamRequestStatus.ASSIGNED; + await _context.SaveChangesAsync(); + var newData = JsonConvert.SerializeObject(examRequest); + await _loggingService.AddWebsiteLog(Request, $"Processed ExamRequest {examRequest.Id}", oldData, newData); + + return Ok(new Response + { + StatusCode = 200, + Message = $"Processed exam request '{examRequest.Id}'", + Data = ExamRequestDto.Parse(examRequest) + }); + } + catch (Exception ex) + { + _logger.LogError("ProcessExamRequest error '{Message}'\n{StackTrace}", ex.Message, ex.StackTrace); + return _sentryHub.CaptureException(ex).ReturnActionResult(); + } + } + + public async Task>> DeleteExamRequest(int examRequestId) + { + try + { + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorTrainingStaffList)) + { + return StatusCode(401); + } + + var examRequest = await _context.ExamRequests + .FirstOrDefaultAsync(x => x.Id == examRequestId); + if (examRequest == null) + { + return NotFound(new Response + { + StatusCode = 404, + Message = $"Exam request '{examRequestId}' not found", + Data = examRequestId + }); + } + + var oldData = JsonConvert.SerializeObject(examRequest); + _context.ExamRequests.Remove(examRequest); + await _context.SaveChangesAsync(); + await _loggingService.AddWebsiteLog(Request, $"Deleted ExamRequest {examRequest.Id}", oldData, string.Empty); + + return Ok(new Response + { + StatusCode = 200, + Message = $"Deleted exam request '{examRequestId}'" + }); + } + catch (Exception ex) + { + _logger.LogError("DeleteExamRequest error '{Message}'\n{StackTrace}", ex.Message, ex.StackTrace); + return _sentryHub.CaptureException(ex).ReturnActionResult(); + } + } +} diff --git a/Memphis.API/Controllers/FeedbackController.cs b/Memphis.API/Controllers/FeedbackController.cs index bdfb896..a4b6bd4 100644 --- a/Memphis.API/Controllers/FeedbackController.cs +++ b/Memphis.API/Controllers/FeedbackController.cs @@ -100,7 +100,7 @@ public async Task>> CreateFeedback(FeedbackPaylo } [HttpGet] - [Authorize(Roles = Constants.CanFeedback)] + [Authorize(Roles = Constants.SeniorTrainingStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(401)] [ProducesResponseType(403)] @@ -111,7 +111,7 @@ public async Task>>> GetAllFeedback( { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanFeedbackList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorTrainingStaffList)) { return StatusCode(401); } @@ -134,7 +134,7 @@ public async Task>>> GetAllFeedback( } [HttpGet("{feedbackId:int}")] - [Authorize(Roles = Constants.CanFeedback)] + [Authorize(Roles = Constants.SeniorTrainingStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(401)] [ProducesResponseType(403)] @@ -144,7 +144,7 @@ public async Task>> GetFeedback(int feedbackId) { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanFeedbackList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorTrainingStaffList)) { return StatusCode(401); } @@ -217,7 +217,7 @@ public async Task>>> GetOwnFeedback( } [HttpPut("{feedbackId:int}")] - [Authorize(Roles = Constants.CanFeedback)] + [Authorize(Roles = Constants.SeniorTrainingStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -228,7 +228,7 @@ public async Task>> UpdateFeedback(int feedbackI { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanFeedbackList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorTrainingStaffList)) { return StatusCode(401); } @@ -281,7 +281,7 @@ public async Task>> UpdateFeedback(int feedbackI } [HttpDelete("{feedbackId:int}")] - [Authorize(Roles = Constants.CanFeedback)] + [Authorize(Roles = Constants.SeniorTrainingStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(401)] [ProducesResponseType(403)] @@ -291,7 +291,7 @@ public async Task>> UpdateFeedback(int feedbackI { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanFeedbackList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorTrainingStaffList)) { return StatusCode(401); } diff --git a/Memphis.API/Controllers/FilesController.cs b/Memphis.API/Controllers/FilesController.cs index 182f1fc..478143e 100644 --- a/Memphis.API/Controllers/FilesController.cs +++ b/Memphis.API/Controllers/FilesController.cs @@ -41,7 +41,7 @@ public FilesController(DatabaseContext context, RedisService redisService, S3Ser } [HttpPost] - [Authorize(Roles = Constants.CanFiles)] + [Authorize(Roles = Constants.FacilitiesStaff)] [ProducesResponseType(typeof(Response), 201)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -52,7 +52,7 @@ public async Task>> CreateFile(FilePayload payload) { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanFilesList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FacilitiesStaffList)) { return StatusCode(401); } @@ -121,7 +121,7 @@ public async Task>>> GetFiles() { try { - var isStaff = await _redisService.ValidateRoles(Request.HttpContext.User, Constants.AllStaffList); + var isStaff = await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FullStaffList); var isSeniorStaff = await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorStaffList); var isTrainingStaff = await _redisService.ValidateRoles(Request.HttpContext.User, Constants.TrainingStaffList); var resultQuery = _context.Files.AsQueryable(); @@ -190,7 +190,7 @@ public async Task>> GetFile(int fileId) } [HttpPut("{fileId:int}")] - [Authorize(Roles = Constants.CanFiles)] + [Authorize(Roles = Constants.FacilitiesStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -201,7 +201,7 @@ public async Task>> UpdateFile(int fileId, FilePaylo { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanFilesList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FacilitiesStaffList)) { return StatusCode(401); } diff --git a/Memphis.API/Controllers/NewsController.cs b/Memphis.API/Controllers/NewsController.cs index 01c47c7..0018a30 100644 --- a/Memphis.API/Controllers/NewsController.cs +++ b/Memphis.API/Controllers/NewsController.cs @@ -36,7 +36,7 @@ public NewsController(DatabaseContext context, RedisService redisService, Loggin } [HttpPost] - [Authorize(Roles = Constants.FullStaff)] + [Authorize(Roles = Constants.MainStaff)] [ProducesResponseType(typeof(Response), 201)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -47,7 +47,7 @@ public async Task>> CreateNews(NewsPayload payload) { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FullStaffList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.MainStaffList)) { return StatusCode(401); } @@ -153,7 +153,7 @@ public async Task>>> GetNewsEntry(int newsId) } [HttpPut("{newsId:int}")] - [Authorize(Roles = Constants.FullStaff)] + [Authorize(Roles = Constants.MainStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -164,7 +164,7 @@ public async Task>> UpdateNews(int newsId, NewsPaylo { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FullStaffList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.MainStaffList)) { return StatusCode(401); } @@ -214,7 +214,7 @@ public async Task>> UpdateNews(int newsId, NewsPaylo } [HttpDelete("{newsId:int}")] - [Authorize(Roles = Constants.FullStaff)] + [Authorize(Roles = Constants.MainStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(401)] [ProducesResponseType(403)] @@ -224,7 +224,7 @@ public async Task>> DeleteNews(int newsId) { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FullStaffList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.MainStaffList)) { return StatusCode(401); } diff --git a/Memphis.API/Controllers/OtsController.cs b/Memphis.API/Controllers/OtsController.cs index 09730d9..31853f2 100644 --- a/Memphis.API/Controllers/OtsController.cs +++ b/Memphis.API/Controllers/OtsController.cs @@ -35,7 +35,7 @@ public OtsController(DatabaseContext context, RedisService redisService, Logging } [HttpGet] - [Authorize(Roles = Constants.CanOts)] + [Authorize(Roles = Constants.SeniorTrainingStaff)] [ProducesResponseType(typeof(ResponsePaging>), 201)] [ProducesResponseType(401)] [ProducesResponseType(403)] @@ -44,7 +44,7 @@ public async Task>>> GetOtsList(int page = 1 { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanOtsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorTrainingStaffList)) { return StatusCode(401); } @@ -90,7 +90,7 @@ public async Task>>> GetOtsList(int page = 1 } [HttpGet("{otsId:int}")] - [Authorize(Roles = Constants.CanOts)] + [Authorize(Roles = Constants.SeniorTrainingStaff)] [ProducesResponseType(typeof(ResponsePaging>), 200)] [ProducesResponseType(401)] [ProducesResponseType(403)] @@ -100,7 +100,7 @@ public async Task>> GetOts(int otsId) { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanOtsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorTrainingStaffList)) { return StatusCode(401); } @@ -134,7 +134,7 @@ public async Task>> GetOts(int otsId) } [HttpPut] - [Authorize(Roles = Constants.CanOts)] + [Authorize(Roles = Constants.SeniorTrainingStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -145,7 +145,7 @@ public async Task>> UpdateOts(UpdateOtsDto payload { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanOtsList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorTrainingStaffList)) { return StatusCode(401); } @@ -218,7 +218,7 @@ public async Task>> UpdateOts(UpdateOtsDto payload } [HttpDelete("{otsId:int}")] - [Authorize(Roles = Constants.CanOts)] + [Authorize(Roles = Constants.SeniorTrainingStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(401)] [ProducesResponseType(403)] diff --git a/Memphis.API/Controllers/ProfileController.cs b/Memphis.API/Controllers/ProfileController.cs new file mode 100644 index 0000000..eb98449 --- /dev/null +++ b/Memphis.API/Controllers/ProfileController.cs @@ -0,0 +1,67 @@ +using Memphis.API.Extensions; +using Memphis.API.Services; +using Memphis.Shared.Data; +using Memphis.Shared.Utils; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; + +namespace Memphis.API.Controllers; + +public class ProfileController : ControllerBase +{ + private readonly DatabaseContext _context; + private readonly LoggingService _loggingService; + private readonly ISentryClient _sentryHub; + private readonly ILogger _logger; + + public ProfileController(DatabaseContext context, LoggingService loggingService, ISentryClient sentryHub, ILogger logger) + { + _context = context; + _loggingService = loggingService; + _sentryHub = sentryHub; + _logger = logger; + } + + [HttpPut] + [Authorize] + [ProducesResponseType(typeof(Response), 200)] + [ProducesResponseType(401)] + [ProducesResponseType(403)] + [ProducesResponseType(typeof(Response), 404)] + [ProducesResponseType(typeof(Response), 500)] + public async Task>> UpdateDiscordId(string discordId) + { + try + { + var user = await HttpContext.GetUser(_context); + if (user == null) + { + return NotFound(new Response + { + StatusCode = 404, + Message = "User not found." + }); + } + + var oldData = JsonConvert.SerializeObject(user); + user.DiscordId = discordId; + user.Updated = DateTimeOffset.UtcNow; + await _context.SaveChangesAsync(); + var newData = JsonConvert.SerializeObject(user); + await _loggingService.AddWebsiteLog(Request, $"'{user.Id}' updated their discord ID", oldData, newData); + + return Ok(new Response + { + StatusCode = 200, + Message = "Discord ID updated successfully.", + Data = user.DiscordId + }); + } + catch (Exception ex) + { + _logger.LogError("UpdateDiscordId error '{Message}'\n{StackTrace}", ex.Message, ex.StackTrace); + return _sentryHub.CaptureException(ex).ReturnActionResult(); + } + } +} diff --git a/Memphis.API/Controllers/SessionsController.cs b/Memphis.API/Controllers/SessionsController.cs index ad6e485..cce04a8 100644 --- a/Memphis.API/Controllers/SessionsController.cs +++ b/Memphis.API/Controllers/SessionsController.cs @@ -77,7 +77,7 @@ public async Task>>> GetSessions(int } [HttpGet("{userId:int}")] - [Authorize(Roles = Constants.AllStaff)] + [Authorize(Roles = Constants.FullStaff)] [ProducesResponseType(401)] [ProducesResponseType(403)] [ProducesResponseType(typeof(Response>), 200)] @@ -86,6 +86,11 @@ public async Task>>> GetUserSessions( { try { + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FullStaffList)) + { + return StatusCode(401); + } + var user = await _context.Users.FindAsync(userId); if (user == null) { diff --git a/Memphis.API/Controllers/StaffingRequestsController.cs b/Memphis.API/Controllers/StaffingRequestsController.cs index 41c3271..f7f13f9 100644 --- a/Memphis.API/Controllers/StaffingRequestsController.cs +++ b/Memphis.API/Controllers/StaffingRequestsController.cs @@ -3,10 +3,13 @@ using Memphis.API.Extensions; using Memphis.API.Services; using Memphis.Shared.Data; +using Memphis.Shared.Dtos; +using Memphis.Shared.Enums; using Memphis.Shared.Models; using Memphis.Shared.Utils; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; namespace Memphis.API.Controllers; @@ -84,4 +87,37 @@ public async Task>> CreateStaffingRequest(Staffing return _sentryHub.CaptureException(ex).ReturnActionResult(); } } + + [HttpGet] + [Authorize(Roles = Constants.FacilitiesStaff)] + [ProducesResponseType(typeof(Response), 200)] + [ProducesResponseType(typeof(Response), 400)] + [ProducesResponseType(typeof(Response), 500)] + public async Task>>> GetStaffingRequests(StaffingRequestStatus status) + { + try + { + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.FacilitiesStaffList)) + { + return StatusCode(401); + } + + var result = await _context.StaffingRequests + .Where(x => x.Status == status) + .OrderByDescending(x => x.Timetstamp) + .ToListAsync(); + + return Ok(new Response> + { + StatusCode = 200, + Message = $"Got {result.Count} staffing requests", + Data = result + }); + } + catch (Exception ex) + { + _logger.LogError("GetStaffingRequests error '{Message}'\n{StackTrace}", ex.Message, ex.StackTrace); + return _sentryHub.CaptureException(ex).ReturnActionResult(); + } + } } diff --git a/Memphis.API/Controllers/TrainingMilestonesController.cs b/Memphis.API/Controllers/TrainingMilestonesController.cs index 35d5080..9c8281e 100644 --- a/Memphis.API/Controllers/TrainingMilestonesController.cs +++ b/Memphis.API/Controllers/TrainingMilestonesController.cs @@ -37,7 +37,7 @@ public TrainingMilestonesController(DatabaseContext context, RedisService redisS } [HttpPost] - [Authorize(Roles = Constants.CanTrainingMilestones)] + [Authorize(Roles = Constants.SeniorTrainingStaff)] [ProducesResponseType(typeof(Response), 201)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -47,7 +47,7 @@ public async Task>> CreateTrainingMiles { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanTrainingMilestonesList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorTrainingStaffList)) { return StatusCode(401); } @@ -149,7 +149,7 @@ public async Task>> GetTrainingMileston } [HttpPut] - [Authorize(Roles = Constants.CanTrainingMilestones)] + [Authorize(Roles = Constants.SeniorTrainingStaff)] [ProducesResponseType(typeof(Response), 200)] [ProducesResponseType(401)] [ProducesResponseType(403)] @@ -159,7 +159,7 @@ public async Task>> UpdateTrainingMiles { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanTrainingMilestonesList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorTrainingStaffList)) { return StatusCode(401); } diff --git a/Memphis.API/Controllers/TrainingSchedulesController.cs b/Memphis.API/Controllers/TrainingSchedulesController.cs index 06edd8d..111572a 100644 --- a/Memphis.API/Controllers/TrainingSchedulesController.cs +++ b/Memphis.API/Controllers/TrainingSchedulesController.cs @@ -38,7 +38,7 @@ public TrainingSchedulesController(DatabaseContext context, RedisService redisSe } [HttpPost] - [Authorize(Roles = Constants.CanTrainingSchedule)] + [Authorize(Roles = Constants.CanRequestTraining)] [ProducesResponseType(typeof(Response), 201)] [ProducesResponseType(typeof(Response>), 400)] [ProducesResponseType(401)] @@ -48,7 +48,7 @@ public async Task>> CreateTrainingSchedu { try { - if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanTrainingMilestonesList)) + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.CanRequestTrainingList)) { return StatusCode(401); } diff --git a/Memphis.API/Controllers/TransferRequestsController.cs b/Memphis.API/Controllers/TransferRequestsController.cs index 80d5768..bb01d85 100644 --- a/Memphis.API/Controllers/TransferRequestsController.cs +++ b/Memphis.API/Controllers/TransferRequestsController.cs @@ -28,7 +28,7 @@ public TransferRequestsController(RedisService redisService, VatusaService vatus } [HttpGet] - [Authorize(Roles = $"{Constants.SeniorStaff},")] + [Authorize(Roles = Constants.SeniorStaff)] [ProducesResponseType(typeof(Response>), 200)] [ProducesResponseType(typeof(Response), 400)] [ProducesResponseType(401)] diff --git a/Memphis.API/Controllers/UsersController.cs b/Memphis.API/Controllers/UsersController.cs index a0ab95a..9b393e8 100644 --- a/Memphis.API/Controllers/UsersController.cs +++ b/Memphis.API/Controllers/UsersController.cs @@ -1,4 +1,5 @@ using FluentValidation; +using FluentValidation.Results; using Memphis.API.Extensions; using Memphis.API.Services; using Memphis.Shared.Data; @@ -8,6 +9,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; namespace Memphis.API.Controllers; @@ -19,21 +21,96 @@ public class UsersController : ControllerBase private readonly DatabaseContext _context; private readonly RedisService _redisService; private readonly LoggingService _loggingService; - public readonly IValidator _validator; + private readonly IValidator _userValidator; + private readonly IValidator _commentValidator; private readonly ISentryClient _sentryHub; private readonly ILogger _logger; public UsersController(DatabaseContext context, RedisService redisService, LoggingService loggingService, - IValidator validator, ISentryClient sentryHub, ILogger logger) + IValidator validator, IValidator commentValidator, ISentryClient sentryHub, ILogger logger) { _context = context; _redisService = redisService; _loggingService = loggingService; - _validator = validator; + _userValidator = validator; + _commentValidator = commentValidator; _sentryHub = sentryHub; _logger = logger; } + [HttpPost] + [Authorize(Roles = Constants.SeniorStaff)] + [ProducesResponseType(typeof(Response), 201)] + [ProducesResponseType(typeof(Response>), 400)] + [ProducesResponseType(401)] + [ProducesResponseType(403)] + [ProducesResponseType(typeof(Response), 500)] + public async Task>> AddComment(int userId, CommentPayload payload) + { + try + { + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorStaffList)) + { + return StatusCode(401); + } + + var validation = await _commentValidator.ValidateAsync(payload); + if (!validation.IsValid) + { + return BadRequest(new Response> + { + StatusCode = 400, + Message = "Validation failure", + Data = validation.Errors + }); + } + + var user = await _context.Users.FirstOrDefaultAsync(x => x.Id == userId); + if (user == null) + { + return NotFound(new Response + { + StatusCode = 404, + Message = "User not found", + Data = userId + }); + } + + var submitter = await HttpContext.GetUser(_context); + if (submitter == null) + { + return NotFound(new Response + { + StatusCode = 404, + Message = "User not found" + }); + } + + var result = await _context.Comments.AddAsync(new Comment + { + User = user, + Submitter = submitter, + Message = payload.Message, + }); + await _context.SaveChangesAsync(); + var newData = JsonConvert.SerializeObject(result.Entity); + await _loggingService.AddWebsiteLog(Request, $"Added comment to user '{user.Id}'", string.Empty, newData); + + return StatusCode(201, new Response + { + StatusCode = 201, + Message = $"Added comment to user '{user.Id}'", + Data = result.Entity + }); + + } + catch (Exception ex) + { + _logger.LogError("GetRoster error '{Message}'\n{StackTrace}", ex.Message, ex.StackTrace); + return _sentryHub.CaptureException(ex).ReturnActionResult(); + } + } + [HttpGet("Roster")] [Authorize] [ProducesResponseType(typeof(Response>), 200)] @@ -136,4 +213,180 @@ public async Task>> GetStaff() return _sentryHub.CaptureException(ex).ReturnActionResult(); } } + + [HttpGet("roles")] + [Authorize(Roles = Constants.AllStaff)] + [ProducesResponseType(typeof(Response>), 200)] + [ProducesResponseType(401)] + [ProducesResponseType(403)] + [ProducesResponseType(typeof(Response), 404)] + [ProducesResponseType(typeof(Response), 500)] + public async Task>>> GetRoles() + { + try + { + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.AllStaffList)) + { + return StatusCode(401); + } + + var roles = await _context.Roles.ToListAsync(); + + return Ok(new Response> + { + StatusCode = 200, + Message = $"Got {roles.Count} roles", + Data = roles + }); + } + catch (Exception ex) + { + _logger.LogError("GetRoles error '{Message}'\n{StackTrace}", ex.Message, ex.StackTrace); + return _sentryHub.CaptureException(ex).ReturnActionResult(); + } + } + + [HttpPut("roles/add/{userId:int}")] + [Authorize(Roles = Constants.SeniorStaff)] + [ProducesResponseType(typeof(Response>), 200)] + [ProducesResponseType(401)] + [ProducesResponseType(403)] + [ProducesResponseType(typeof(Response), 404)] + [ProducesResponseType(typeof(Response), 500)] + public async Task>>> AddRole(int userId, int roleId) + { + try + { + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorStaffList)) + { + return StatusCode(401); + } + + var user = await _context.Users + .Include(x => x.Roles) + .FirstOrDefaultAsync(x => x.Id == userId); + if (user == null) + { + return NotFound(new Response + { + StatusCode = 404, + Message = "User not found", + Data = userId + }); + } + + var role = await _context.Roles + .FirstOrDefaultAsync(x => x.Id == roleId); + if (role == null) + { + return NotFound(new Response + { + StatusCode = 404, + Message = "Role not found", + Data = roleId + }); + } + + user.Roles ??= []; + + if (user.Roles.Any(x => x.Id == roleId)) + { + return Ok(new Response> + { + StatusCode = 200, + Message = "Role already assigned to user", + Data = user.Roles?.ToList() + }); + } + + var oldData = JsonConvert.SerializeObject(user.Roles); + user.Roles.Add(role); + await _context.SaveChangesAsync(); + var newData = JsonConvert.SerializeObject(user.Roles); + await _loggingService.AddWebsiteLog(Request, $"Added Role '{role.Name}' to user '{user.Id}'", oldData, newData); + + return Ok(new Response> + { + StatusCode = 200, + Message = $"Added role '{role.Name}' to user '{user.Id}'", + Data = user.Roles.ToList() + }); + } + catch (Exception ex) + { + _logger.LogError("AddRole error '{Message}'\n{StackTrace}", ex.Message, ex.StackTrace); + return _sentryHub.CaptureException(ex).ReturnActionResult(); + } + } + + [HttpPut("roles/remove/{userId:int}")] + [Authorize(Roles = Constants.SeniorStaff)] + [ProducesResponseType(typeof(Response>), 200)] + [ProducesResponseType(401)] + [ProducesResponseType(403)] + [ProducesResponseType(typeof(Response), 404)] + [ProducesResponseType(typeof(Response), 500)] + public async Task>>> RemoveRole(int userId, int roleId) + { + try + { + if (!await _redisService.ValidateRoles(Request.HttpContext.User, Constants.SeniorStaffList)) + { + return StatusCode(401); + } + + var user = await _context.Users + .Include(x => x.Roles) + .FirstOrDefaultAsync(x => x.Id == userId); + if (user == null) + { + return NotFound(new Response + { + StatusCode = 404, + Message = "User not found", + Data = userId + }); + } + + var role = await _context.Roles + .FirstOrDefaultAsync(x => x.Id == roleId); + if (role == null) + { + return NotFound(new Response + { + StatusCode = 404, + Message = "Role not found", + Data = roleId + }); + } + + if (user.Roles == null || !user.Roles.Any(x => x.Id == roleId)) + { + return Ok(new Response> + { + StatusCode = 200, + Message = "Role not assigned to user", + Data = user.Roles?.ToList() + }); + } + + var oldData = JsonConvert.SerializeObject(user.Roles); + user.Roles.Remove(role); + await _context.SaveChangesAsync(); + var newData = JsonConvert.SerializeObject(user.Roles); + await _loggingService.AddWebsiteLog(Request, $"Removed Role '{role.Name}' from user '{user.Id}'", oldData, newData); + + return Ok(new Response> + { + StatusCode = 200, + Message = $"Removed role '{role.Name}' from user '{user.Id}'", + Data = user.Roles.ToList() + }); + } + catch (Exception ex) + { + _logger.LogError("AddRole error '{Message}'\n{StackTrace}", ex.Message, ex.StackTrace); + return _sentryHub.CaptureException(ex).ReturnActionResult(); + } + } } diff --git a/Memphis.API/Extensions/HttpContextExtensions.cs b/Memphis.API/Extensions/HttpContextExtensions.cs index 5e75ab1..5c4acc6 100644 --- a/Memphis.API/Extensions/HttpContextExtensions.cs +++ b/Memphis.API/Extensions/HttpContextExtensions.cs @@ -64,12 +64,12 @@ public static async Task IsTrainingStaff(this HttpContext httpContext, Red public static async Task IsAllStaff(this HttpContext httpContext, RedisService redisService) { - return await redisService.ValidateRoles(httpContext.User, Constants.AllStaffList); + return await redisService.ValidateRoles(httpContext.User, Constants.FullStaffList); } public static async Task IsFullStaff(this HttpContext httpContext, RedisService redisService) { - return await redisService.ValidateRoles(httpContext.User, Constants.FullStaffList); + return await redisService.ValidateRoles(httpContext.User, Constants.MainStaffList); } public static async Task IsSeniorStaff(this HttpContext httpContext, RedisService redisService) diff --git a/Memphis.API/Program.cs b/Memphis.API/Program.cs index 2e91267..1b97a32 100644 --- a/Memphis.API/Program.cs +++ b/Memphis.API/Program.cs @@ -104,6 +104,7 @@ builder.Services.AddScoped, EventPositionValidator>(); builder.Services.AddScoped, EventRegistrationValidator>(); builder.Services.AddScoped, EventValidator>(); +builder.Services.AddScoped, ExamRequestValidator>(); builder.Services.AddScoped, FacilityValidator>(); builder.Services.AddScoped, FeedbackValidator>(); builder.Services.AddScoped, FileValidator>(); diff --git a/Memphis.API/Validators/ExamRequestValidator.cs b/Memphis.API/Validators/ExamRequestValidator.cs new file mode 100644 index 0000000..00e6fe2 --- /dev/null +++ b/Memphis.API/Validators/ExamRequestValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; +using Memphis.Shared.Models; + +namespace Memphis.API.Validators; + +public class ExamRequestValidator : AbstractValidator +{ + public ExamRequestValidator() + { + RuleFor(x => x.StudentId).NotEmpty(); + RuleFor(x => x.ExamId).NotEmpty(); + RuleFor(x => x.Reason).NotEmpty(); + } +} diff --git a/Memphis.Shared/Dtos/ExamRequestDto.cs b/Memphis.Shared/Dtos/ExamRequestDto.cs new file mode 100644 index 0000000..4d3639b --- /dev/null +++ b/Memphis.Shared/Dtos/ExamRequestDto.cs @@ -0,0 +1,34 @@ +using Memphis.Shared.Enums; +using Memphis.Shared.Models; + +namespace Memphis.Shared.Dtos; + +public class ExamRequestDto +{ + public int Id { get; set; } + public required string Instructor { get; set; } + public required string Student { get; set; } + public required Exam Exam { get; set; } + public required string Reason { get; set; } + public ExamRequestStatus Status { get; set; } + public DateTimeOffset Created { get; set; } + + public static ExamRequestDto Parse(ExamRequest examRequest) + { + return new ExamRequestDto + { + Id = examRequest.Id, + Instructor = $"{examRequest.Instructor.FirstName} {examRequest.Instructor.LastName}", + Student = $"{examRequest.Student.FirstName} {examRequest.Student.LastName}", + Exam = examRequest.Exam, + Reason = examRequest.Reason, + Status = examRequest.Status, + Created = examRequest.Created + }; + } + + public static IList ParseMany(IList examRequests) + { + return examRequests.Select(Parse).ToList(); + } +} diff --git a/Memphis.Shared/Models/Comment.cs b/Memphis.Shared/Models/Comment.cs index 03b17d7..d0ade77 100644 --- a/Memphis.Shared/Models/Comment.cs +++ b/Memphis.Shared/Models/Comment.cs @@ -5,7 +5,6 @@ public class Comment public int Id { get; set; } public required User User { get; set; } public required User Submitter { get; set; } - public bool Confidential { get; set; } public required string Message { get; set; } public DateTimeOffset Timestamp { get; set; } = DateTimeOffset.UtcNow; } @@ -13,6 +12,5 @@ public class Comment public class CommentPayload { public int UserId { get; set; } - public bool Confidential { get; set; } public required string Message { get; set; } } \ No newline at end of file diff --git a/Memphis.Shared/Models/ExamRequest.cs b/Memphis.Shared/Models/ExamRequest.cs index f1a8563..45eb170 100644 --- a/Memphis.Shared/Models/ExamRequest.cs +++ b/Memphis.Shared/Models/ExamRequest.cs @@ -12,3 +12,10 @@ public class ExamRequest public ExamRequestStatus Status { get; set; } = ExamRequestStatus.PENDING; public DateTimeOffset Created { get; set; } = DateTimeOffset.UtcNow; } + +public class ExamRequestPayload +{ + public int StudentId { get; set; } + public int ExamId { get; set; } + public required string Reason { get; set; } +} \ No newline at end of file diff --git a/Memphis.Shared/Models/StaffingRequest.cs b/Memphis.Shared/Models/StaffingRequest.cs index 30dc09f..6a5160f 100644 --- a/Memphis.Shared/Models/StaffingRequest.cs +++ b/Memphis.Shared/Models/StaffingRequest.cs @@ -13,6 +13,7 @@ public class StaffingRequest public StaffingRequestStatus Status { get; set; } = StaffingRequestStatus.PENDING; public DateTimeOffset Start { get; set; } public TimeSpan Duration { get; set; } + public DateTimeOffset Timetstamp { get; set; } = DateTimeOffset.UtcNow; } public class StaffingRequestPayload diff --git a/Memphis.Shared/Utils/Constants.cs b/Memphis.Shared/Utils/Constants.cs index e49591d..3aa6cd5 100644 --- a/Memphis.Shared/Utils/Constants.cs +++ b/Memphis.Shared/Utils/Constants.cs @@ -3,26 +3,16 @@ public class Constants { public const string SeniorStaff = "ATM,DATM,TA,WM"; - public const string FullStaff = "ATM,DATM,TA,WM,EC,FE"; - public const string AllStaff = "ATM,DATM,TA,ATA,WM,AWM,EC,AEC,FE,AFE"; - public const string SeniorTrainingStaff = "TA,ATA,INS,WN"; + public const string MainStaff = "ATM,DATM,TA,WM,EC,FE"; + public const string FullStaff = "ATM,DATM,TA,ATA,WM,AWM,EC,AEC,FE,AFE"; + public const string AllStaff = "ATM,DATM,TA,ATA,INS,MTR,WM,AWM,EC,AEC,FE,AFE,INT,MTR"; + public const string SeniorTrainingStaff = "ATM,DATM,TA,ATA,INS,WM"; public const string TrainingStaff = "TA,ATA,INS,MTR,WM"; public const string CanRegisterForEvents = "CanRegisterForEvents"; public const string CanRequestTraining = "CanRequestTraining"; - public const string CanAirports = "ATM,DATM,TA,WM,ATM,FE,AFE"; - public const string CanManageCertifications = "ATM,DATM,TA,WM,AWM"; - public const string CanComment = "ATM,DATM,TA,ATA,WM,AWM,EX,AEC,FE,AFE,INS,MTR"; - public const string CanCommentConfidential = "ATM,DATM,TA,WM"; - public const string CanEmailLog = "ATM,DATM,TA,WM,AWM"; - public const string CanEvents = "ATM,DATM,TA,WM,EC,AEC"; - public const string CanFaq = "ATM,DATM,TA,ATA,WM,AWM,EC,AEC,FE,AFE"; - public const string CanEmailLogs = "ATM,DATM,TA,WM,AWM"; - public const string CanFeedback = "ATM,DATM,TA,ATA,WM,INS"; - public const string CanFiles = "ATM,DATM,TA,WM,FE,AFE"; - public const string CanAnnouncement = "ATM,DATM,TA,WM,EC,FE"; - public const string CanOts = "ATM,DATM,TA,ATA,WM,AWM,INS"; - public const string CanTrainingMilestones = "ATM,DATM,TA,ATA,WM,AWM"; - public const string CanTrainingSchedule = "ATM,DATM,TA,ATA,WM,AWM,INS,MTR"; + public const string WebStaff = "ATM,DATM,TA,ATA,WM,AWM,WEB"; + public const string EventsStaff = "ATM,DATM,TA,ATA,WM,AWM,EC,AEC,EVENTS"; + public const string FacilitiesStaff = "ATM,DATM,TA,ATA,WM,AWM,FE,AFE,FACILITIES"; public static readonly string[] SeniorStaffList = { @@ -32,7 +22,7 @@ public class Constants "WM" }; - public static readonly string[] FullStaffList = + public static readonly string[] MainStaffList = { "ATM", "DATM", @@ -42,7 +32,7 @@ public class Constants "FE" }; - public static readonly string[] AllStaffList = + public static readonly string[] FullStaffList = { "ATM", "DATM", @@ -56,49 +46,14 @@ public class Constants "AFE" }; - public static readonly string[] SeniorTrainingStaffList = - { - "TA", - "ATA", - "INS", - "WM" - }; - - public static readonly string[] TrainingStaffList = - { - "TA", - "ATA", - "INS", - "MTR", - "WM" - }; - - public static readonly string[] CanAirportsList = - { - "ATM", - "DATM", - "TA", - "WM", - "AWM", - "FE", - "AFE" - }; - - public static readonly string[] CanManageCertificationsList = - { - "ATM", - "DATM", - "TA", - "WM", - "AWM" - }; - - public static readonly string[] CanCommentList = + public static readonly string[] AllStaffList = { "ATM", "DATM", "TA", "ATA", + "INS", + "MTR", "WM", "AWM", "EC", @@ -109,87 +64,34 @@ public class Constants "MTR" }; - public static readonly string[] CanCommentConfidentialList = - { - "ATM", - "DATM", - "TA", - "WM" - }; - - public static readonly string[] CanEmailLogList = - { - "ATM", - "DATM", - "TA", - "WM", - "AWM" - }; - - public static readonly string[] CanEventsList = - { - "ATM", - "DATM", - "TA", - "WM", - "EC", - "AEC" - }; - - public static readonly string[] CanFaqList = + public static readonly string[] SeniorTrainingStaffList = { - "ATM", - "DATM", "TA", "ATA", - "WM", - "AWM", - "EC", - "AEC", - "FE", - "AFE" - }; - - public static readonly string[] CanEmailLogsList = - { - "ATM", - "DATM", - "TA", - "WM", - "AWM" + "INS", + "WM" }; - public static readonly string[] CanFeedbackList = + public static readonly string[] TrainingStaffList = { - "ATM", - "DATM", "TA", "ATA", - "WM", - "INS" + "INS", + "MTR", + "WM" }; - public static readonly string[] CanFilesList = + public static readonly string[] CanRegisterForEventsList = { - "ATM", - "DATM", - "TA", - "WM", - "FE", - "AFE" + "CanRegisterForEvents" }; - public static readonly string[] CanAnnouncementList = + public static readonly string[] CanRequestTrainingList = { - "ATM", - "DATM", - "TA", - "WM", - "EC", - "FE" + "CanRequestTraining" }; - public static readonly string[] CanOtsList = + public static readonly string[] WebStaffList = { "ATM", "DATM", @@ -197,20 +99,23 @@ public class Constants "ATA", "WM", "AWM", - "INS" + "WEB" }; - public static readonly string[] CanTrainingMilestonesList = + public static readonly string[] EventsStaffList = { "ATM", "DATM", "TA", "ATA", "WM", - "AWM" + "AWM", + "EC", + "AEC", + "EVENTS" }; - public static readonly string[] CanTrainingScheduleList = + public static readonly string[] FacilitiesStaffList = { "ATM", "DATM", @@ -218,7 +123,8 @@ public class Constants "ATA", "WM", "AWM", - "INS", - "MTR" + "FE", + "AFE", + "FACILITIES" }; } \ No newline at end of file From b7ce5c113cabd83a4534ba651c41b870303072fe Mon Sep 17 00:00:00 2001 From: fixterjake Date: Tue, 17 Jun 2025 11:14:47 -0400 Subject: [PATCH 2/2] Update Memphis.API/Controllers/UsersController.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Memphis.API/Controllers/UsersController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Memphis.API/Controllers/UsersController.cs b/Memphis.API/Controllers/UsersController.cs index 9b393e8..d022d11 100644 --- a/Memphis.API/Controllers/UsersController.cs +++ b/Memphis.API/Controllers/UsersController.cs @@ -106,7 +106,7 @@ public async Task>> AddComment(int userId, Commen } catch (Exception ex) { - _logger.LogError("GetRoster error '{Message}'\n{StackTrace}", ex.Message, ex.StackTrace); + _logger.LogError("AddComment error '{Message}'\n{StackTrace}", ex.Message, ex.StackTrace); return _sentryHub.CaptureException(ex).ReturnActionResult(); } }