Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions api/CourseRegistration.API/Controllers/CoursesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ public async Task<ActionResult<ApiResponseDto<PagedResponseDto<CourseDto>>>> Get
_logger.LogInformation("Getting courses with page: {Page}, pageSize: {PageSize}, searchTerm: {SearchTerm}, instructor: {Instructor}",
page, pageSize, searchTerm, instructor);

var result = await _courseService.GetCoursesAsync(page, pageSize, searchTerm, instructor);
return Ok(ApiResponseDto<PagedResponseDto<CourseDto>>.SuccessResponse(result, "Courses retrieved successfully"));
var pagedCourses = await _courseService.GetCoursesAsync(page, pageSize, searchTerm, instructor);
return Ok(ApiResponseDto<PagedResponseDto<CourseDto>>.SuccessResponse(pagedCourses, "Courses retrieved successfully"));
}

/// <summary>
Expand Down Expand Up @@ -125,8 +125,8 @@ public async Task<ActionResult<ApiResponseDto<object>>> DeleteCourse(Guid id)
{
_logger.LogInformation("Deleting course with ID: {CourseId}", id);

var result = await _courseService.DeleteCourseAsync(id);
if (!result)
var isCourseDeleted = await _courseService.DeleteCourseAsync(id);
if (!isCourseDeleted)
{
return NotFound(ApiResponseDto<object>.ErrorResponse("Course not found"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ public async Task<ActionResult<ApiResponseDto<PagedResponseDto<RegistrationDto>>
_logger.LogInformation("Getting registrations with page: {Page}, pageSize: {PageSize}, studentId: {StudentId}, courseId: {CourseId}, status: {Status}",
page, pageSize, studentId, courseId, status);

var result = await _registrationService.GetRegistrationsAsync(page, pageSize, studentId, courseId, status);
return Ok(ApiResponseDto<PagedResponseDto<RegistrationDto>>.SuccessResponse(result, "Registrations retrieved successfully"));
var pagedRegistrations = await _registrationService.GetRegistrationsAsync(page, pageSize, studentId, courseId, status);
return Ok(ApiResponseDto<PagedResponseDto<RegistrationDto>>.SuccessResponse(pagedRegistrations, "Registrations retrieved successfully"));
}

/// <summary>
Expand Down Expand Up @@ -132,8 +132,8 @@ public async Task<ActionResult<ApiResponseDto<object>>> CancelRegistration(Guid
{
_logger.LogInformation("Cancelling registration with ID: {RegistrationId}", id);

var result = await _registrationService.CancelRegistrationAsync(id);
if (!result)
var isRegistrationCancelled = await _registrationService.CancelRegistrationAsync(id);
if (!isRegistrationCancelled)
{
return NotFound(ApiResponseDto<object>.ErrorResponse("Registration not found"));
}
Expand Down
8 changes: 4 additions & 4 deletions api/CourseRegistration.API/Controllers/StudentsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ public async Task<ActionResult<ApiResponseDto<PagedResponseDto<StudentDto>>>> Ge
{
_logger.LogInformation("Getting students with page: {Page}, pageSize: {PageSize}", page, pageSize);

var result = await _studentService.GetStudentsAsync(page, pageSize);
return Ok(ApiResponseDto<PagedResponseDto<StudentDto>>.SuccessResponse(result, "Students retrieved successfully"));
var pagedStudents = await _studentService.GetStudentsAsync(page, pageSize);
return Ok(ApiResponseDto<PagedResponseDto<StudentDto>>.SuccessResponse(pagedStudents, "Students retrieved successfully"));
}

/// <summary>
Expand Down Expand Up @@ -119,8 +119,8 @@ public async Task<ActionResult<ApiResponseDto<object>>> DeleteStudent(Guid id)
{
_logger.LogInformation("Deleting student with ID: {StudentId}", id);

var result = await _studentService.DeleteStudentAsync(id);
if (!result)
var isStudentDeleted = await _studentService.DeleteStudentAsync(id);
if (!isStudentDeleted)
{
return NotFound(ApiResponseDto<object>.ErrorResponse("Student not found"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ private async Task HandleExceptionAsync(HttpContext context, Exception exception

_logger.LogError(exception, "An unhandled exception occurred. CorrelationId: {CorrelationId}", correlationId);

var response = context.Response;
response.ContentType = "application/json";
var httpResponse = context.Response;
httpResponse.ContentType = "application/json";

var errorResponse = new ApiResponseDto<object>
var apiErrorResponse = new ApiResponseDto<object>
{
Success = false,
Data = null
Expand All @@ -57,58 +57,58 @@ private async Task HandleExceptionAsync(HttpContext context, Exception exception
switch (exception)
{
case ArgumentException argEx:
response.StatusCode = (int)HttpStatusCode.BadRequest;
errorResponse.Message = "Invalid argument provided";
errorResponse.Errors = new[] { argEx.Message };
httpResponse.StatusCode = (int)HttpStatusCode.BadRequest;
apiErrorResponse.Message = "Invalid argument provided";
apiErrorResponse.Errors = new[] { argEx.Message };
break;

case InvalidOperationException invOpEx:
response.StatusCode = (int)HttpStatusCode.BadRequest;
errorResponse.Message = "Invalid operation";
errorResponse.Errors = new[] { invOpEx.Message };
httpResponse.StatusCode = (int)HttpStatusCode.BadRequest;
apiErrorResponse.Message = "Invalid operation";
apiErrorResponse.Errors = new[] { invOpEx.Message };
break;

case UnauthorizedAccessException:
response.StatusCode = (int)HttpStatusCode.Unauthorized;
errorResponse.Message = "Unauthorized access";
errorResponse.Errors = new[] { "You are not authorized to perform this action" };
httpResponse.StatusCode = (int)HttpStatusCode.Unauthorized;
apiErrorResponse.Message = "Unauthorized access";
apiErrorResponse.Errors = new[] { "You are not authorized to perform this action" };
break;

case KeyNotFoundException:
response.StatusCode = (int)HttpStatusCode.NotFound;
errorResponse.Message = "Resource not found";
errorResponse.Errors = new[] { "The requested resource was not found" };
httpResponse.StatusCode = (int)HttpStatusCode.NotFound;
apiErrorResponse.Message = "Resource not found";
apiErrorResponse.Errors = new[] { "The requested resource was not found" };
break;

case TimeoutException:
response.StatusCode = (int)HttpStatusCode.RequestTimeout;
errorResponse.Message = "Request timeout";
errorResponse.Errors = new[] { "The request timed out" };
httpResponse.StatusCode = (int)HttpStatusCode.RequestTimeout;
apiErrorResponse.Message = "Request timeout";
apiErrorResponse.Errors = new[] { "The request timed out" };
break;

default:
response.StatusCode = (int)HttpStatusCode.InternalServerError;
errorResponse.Message = "An internal server error occurred";
httpResponse.StatusCode = (int)HttpStatusCode.InternalServerError;
apiErrorResponse.Message = "An internal server error occurred";

// In development, include the full exception details
if (context.RequestServices.GetService<IWebHostEnvironment>()?.IsDevelopment() == true)
{
errorResponse.Errors = new[] { exception.Message, exception.StackTrace ?? string.Empty };
apiErrorResponse.Errors = new[] { exception.Message, exception.StackTrace ?? string.Empty };
}
else
{
errorResponse.Errors = new[] { "Please contact support with correlation ID: " + correlationId };
apiErrorResponse.Errors = new[] { "Please contact support with correlation ID: " + correlationId };
}
break;
}

var jsonOptions = new JsonSerializerOptions
var camelCaseJsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};

var jsonResponse = JsonSerializer.Serialize(errorResponse, jsonOptions);
await response.WriteAsync(jsonResponse);
var jsonResponse = JsonSerializer.Serialize(apiErrorResponse, camelCaseJsonOptions);
await httpResponse.WriteAsync(jsonResponse);
}
}
58 changes: 29 additions & 29 deletions api/CourseRegistration.Application/Services/AdminAccessChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,65 +275,65 @@ private static bool IsOutsideBusinessHours(DateTime currentTime)
/// <returns>AdminValidationResult with detailed validation information</returns>
public static AdminValidationResult ValidateAdminAccess(User? user, ClaimsPrincipal? principal, ILogger? logger = null)
{
var result = new AdminValidationResult();
var adminValidationResult = new AdminValidationResult();

try
{
// User object validation
result.UserObjectValid = user != null;
if (!result.UserObjectValid)
adminValidationResult.UserObjectValid = user != null;
if (!adminValidationResult.UserObjectValid)
{
result.FailureReason = "User object is null";
logger?.LogWarning("Admin validation failed: {Reason}", result.FailureReason);
return result;
adminValidationResult.FailureReason = "User object is null";
logger?.LogWarning("Admin validation failed: {Reason}", adminValidationResult.FailureReason);
return adminValidationResult;
}

// Account status validation
result.AccountActive = user!.IsActive;
if (!result.AccountActive)
adminValidationResult.AccountActive = user!.IsActive;
if (!adminValidationResult.AccountActive)
{
result.FailureReason = "User account is inactive";
logger?.LogWarning("Admin validation failed for user {UserId}: {Reason}", user.UserId, result.FailureReason);
return result;
adminValidationResult.FailureReason = "User account is inactive";
logger?.LogWarning("Admin validation failed for user {UserId}: {Reason}", user.UserId, adminValidationResult.FailureReason);
return adminValidationResult;
}

// Role validation
result.HasValidRole = user.Role == UserRole.Admin || user.Role == UserRole.SuperAdmin;
if (!result.HasValidRole)
adminValidationResult.HasValidRole = user.Role == UserRole.Admin || user.Role == UserRole.SuperAdmin;
if (!adminValidationResult.HasValidRole)
{
result.FailureReason = $"User has insufficient role: {user.Role}";
logger?.LogInformation("Admin validation failed for user {UserId}: {Reason}", user.UserId, result.FailureReason);
return result;
adminValidationResult.FailureReason = $"User has insufficient role: {user.Role}";
logger?.LogInformation("Admin validation failed for user {UserId}: {Reason}", user.UserId, adminValidationResult.FailureReason);
return adminValidationResult;
}

// Claims validation
result.ClaimsValid = HasAdminAccessFromClaims(principal, logger);
if (!result.ClaimsValid)
adminValidationResult.ClaimsValid = HasAdminAccessFromClaims(principal, logger);
if (!adminValidationResult.ClaimsValid)
{
result.FailureReason = "Invalid or missing claims";
return result;
adminValidationResult.FailureReason = "Invalid or missing claims";
return adminValidationResult;
}

// Time-based validation
result.WithinAllowedTime = ValidateSecureAdminAccess(user, DateTime.UtcNow, logger);
if (!result.WithinAllowedTime)
adminValidationResult.WithinAllowedTime = ValidateSecureAdminAccess(user, DateTime.UtcNow, logger);
if (!adminValidationResult.WithinAllowedTime)
{
result.FailureReason = "Access denied due to time restrictions";
return result;
adminValidationResult.FailureReason = "Access denied due to time restrictions";
return adminValidationResult;
}

result.IsValid = true;
result.ValidatedAt = DateTime.UtcNow;
adminValidationResult.IsValid = true;
adminValidationResult.ValidatedAt = DateTime.UtcNow;

logger?.LogInformation("Admin access validation successful for user {UserId}", user.UserId);

return result;
return adminValidationResult;
}
catch (Exception ex)
{
result.FailureReason = "Validation error occurred";
adminValidationResult.FailureReason = "Validation error occurred";
logger?.LogError(ex, "Error during admin access validation for user {UserId}", user?.UserId);
return result;
return adminValidationResult;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ public virtual async Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> pr
/// </summary>
public virtual async Task<T> AddAsync(T entity)
{
var result = await _dbSet.AddAsync(entity);
return result.Entity;
var addedEntityEntry = await _dbSet.AddAsync(entity);
return addedEntityEntry.Entity;
}

/// <summary>
Expand Down
36 changes: 18 additions & 18 deletions frontend/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const API_BASE_URL = 'http://localhost:5210/api';

// DOM Elements
const addCourseForm = document.getElementById('add-course-form');
const submitBtn = document.getElementById('submit-btn');
const submitButton = document.getElementById('submit-btn');
const submitText = document.getElementById('submit-text');
const loadingOverlay = document.getElementById('loading-overlay');

Expand Down Expand Up @@ -61,12 +61,12 @@ function initializeForm() {
addCourseForm.addEventListener('submit', handleFormSubmit);

// Instructor name validation (letters, spaces, periods only)
document.getElementById('instructorName').addEventListener('input', function(e) {
const value = e.target.value;
document.getElementById('instructorName').addEventListener('input', function(inputEvent) {
const value = inputEvent.target.value;
const validPattern = /^[a-zA-Z\s.]*$/;

if (!validPattern.test(value)) {
e.target.value = value.replace(/[^a-zA-Z\s.]/g, '');
inputEvent.target.value = value.replace(/[^a-zA-Z\s.]/g, '');
}
});

Expand All @@ -80,8 +80,8 @@ function initializeForm() {
}

// Form Submission Handler
async function handleFormSubmit(e) {
e.preventDefault();
async function handleFormSubmit(submitEvent) {
submitEvent.preventDefault();

if (!validateForm()) {
return;
Expand Down Expand Up @@ -252,8 +252,8 @@ function clearAllErrors() {
const errorElements = document.querySelectorAll('.error-message');
const inputElements = document.querySelectorAll('.form-input, .form-textarea');

errorElements.forEach(el => el.textContent = '');
inputElements.forEach(el => el.classList.remove('error'));
errorElements.forEach(errorElement => errorElement.textContent = '');
inputElements.forEach(inputElement => inputElement.classList.remove('error'));
}

function displayValidationErrors(errors) {
Expand Down Expand Up @@ -285,13 +285,13 @@ function mapFieldName(apiFieldName) {

// UI State Functions
function setSubmitButton(loading, text) {
submitBtn.disabled = loading;
submitButton.disabled = loading;
submitText.textContent = text;

if (loading) {
submitBtn.innerHTML = `<i class="fas fa-spinner fa-spin"></i> ${text}`;
submitButton.innerHTML = `<i class="fas fa-spinner fa-spin"></i> ${text}`;
} else {
submitBtn.innerHTML = `<i class="fas fa-plus"></i> ${text}`;
submitButton.innerHTML = `<i class="fas fa-plus"></i> ${text}`;
}
}

Expand Down Expand Up @@ -566,18 +566,18 @@ function getCourseStatusText(startDate, endDate) {
}

// Keyboard Navigation
document.addEventListener('keydown', function(e) {
document.addEventListener('keydown', function(keyboardEvent) {
// Close modals with Escape key
if (e.key === 'Escape') {
if (keyboardEvent.key === 'Escape') {
const openModals = document.querySelectorAll('.modal:not(.hidden)');
openModals.forEach(modal => modal.classList.add('hidden'));
}
});

// Click outside modal to close
document.addEventListener('click', function(e) {
if (e.target.classList.contains('modal')) {
e.target.classList.add('hidden');
document.addEventListener('click', function(clickEvent) {
if (clickEvent.target.classList.contains('modal')) {
clickEvent.target.classList.add('hidden');
}
});

Expand Down Expand Up @@ -760,8 +760,8 @@ async function loadCoursesForDropdown() {
document.addEventListener('DOMContentLoaded', function() {
const createRegistrationForm = document.getElementById('create-registration-form');
if (createRegistrationForm) {
createRegistrationForm.addEventListener('submit', async function(e) {
e.preventDefault();
createRegistrationForm.addEventListener('submit', async function(submitEvent) {
submitEvent.preventDefault();

const formData = {
studentId: document.getElementById('registration-student').value,
Expand Down
Loading