mirror of
https://github.com/opelly27/WinStudentGoalTracker.git
synced 2026-05-20 06:27:37 +00:00
Updates to encompass benchmarks
This commit is contained in:
@@ -130,6 +130,39 @@ public class StudentController : BaseController
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{idStudent:guid}/benchmarks")]
|
||||
[Authorize(Roles = $"{UserRoles.Teacher},{UserRoles.Paraeducator},{UserRoles.ProgramAdmin}")]
|
||||
[ProducesResponseType(typeof(ResponseResult<StudentBenchmarkSummary>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ResponseResult<StudentBenchmarkSummary>), StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<ResponseResult<StudentBenchmarkSummary>>> GetBenchmarks(Guid idStudent)
|
||||
{
|
||||
var (userId, email, programId, role, error) = GetProgramUserFromClaims();
|
||||
if (error is not null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
var students = await _studentRepository.GetMyStudentsAsync(userId, programId, role);
|
||||
|
||||
if (!students.Select(s => s.StudentId).Contains(idStudent))
|
||||
{
|
||||
return NotFound(new ResponseResult<StudentBenchmarkSummary>
|
||||
{
|
||||
Success = false,
|
||||
Message = "Student not found."
|
||||
});
|
||||
}
|
||||
|
||||
var summary = await _studentRepository.GetBenchmarkSummaryAsync(idStudent);
|
||||
|
||||
return Ok(new ResponseResult<StudentBenchmarkSummary>
|
||||
{
|
||||
Success = true,
|
||||
Message = "Benchmarks retrieved successfully.",
|
||||
Data = summary
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("{idStudent:guid}/goals")]
|
||||
[Authorize(Roles = $"{UserRoles.Teacher},{UserRoles.ProgramAdmin}")]
|
||||
[ProducesResponseType(typeof(ResponseResult<StudentGoalItem>), StatusCodes.Status201Created)]
|
||||
@@ -205,6 +238,38 @@ public class StudentController : BaseController
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPut("{idStudent:guid}/goals/{idGoal:guid}")]
|
||||
[Authorize(Roles = $"{UserRoles.Teacher},{UserRoles.ProgramAdmin}")]
|
||||
[ProducesResponseType(typeof(ResponseResult<object>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ResponseResult<object>), StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<ResponseResult<object>>> UpdateGoal(Guid idStudent, Guid idGoal, [FromBody] UpdateGoalDto dto)
|
||||
{
|
||||
var (userId, email, programId, role, error) = GetProgramUserFromClaims();
|
||||
if (error is not null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
var students = await _studentRepository.GetMyStudentsAsync(userId, programId, role);
|
||||
|
||||
if (!students.Select(s => s.StudentId).Contains(idStudent))
|
||||
{
|
||||
return NotFound(new ResponseResult<object>
|
||||
{
|
||||
Success = false,
|
||||
Message = "Student not found."
|
||||
});
|
||||
}
|
||||
|
||||
var updated = await _studentRepository.UpdateGoalAsync(idGoal, dto);
|
||||
|
||||
return Ok(new ResponseResult<object>
|
||||
{
|
||||
Success = true,
|
||||
Message = updated ? "Goal updated successfully." : "No changes were applied."
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("{idStudent:guid}/progress-event")]
|
||||
[Authorize(Roles = $"{UserRoles.Teacher},{UserRoles.Paraeducator},{UserRoles.ProgramAdmin}")]
|
||||
[ProducesResponseType(typeof(ResponseResult), StatusCodes.Status201Created)]
|
||||
@@ -411,4 +476,87 @@ public class StudentController : BaseController
|
||||
Message = "Student deleted."
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("{idStudent:guid}/benchmarks")]
|
||||
[Authorize(Roles = $"{UserRoles.Teacher},{UserRoles.ProgramAdmin}")]
|
||||
[ProducesResponseType(typeof(ResponseResult<StudentBenchmarkItem>), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ResponseResult<StudentBenchmarkItem>), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ResponseResult<StudentBenchmarkItem>), StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<ResponseResult<StudentBenchmarkItem>>> CreateBenchmark(Guid idStudent, [FromBody] CreateBenchmarkDto dto)
|
||||
{
|
||||
var (userId, email, programId, role, error) = GetProgramUserFromClaims();
|
||||
if (error is not null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
var students = await _studentRepository.GetMyStudentsAsync(userId, programId, role);
|
||||
|
||||
if (!students.Select(s => s.StudentId).Contains(idStudent))
|
||||
{
|
||||
return NotFound(new ResponseResult<StudentBenchmarkItem>
|
||||
{
|
||||
Success = false,
|
||||
Message = "Student not found."
|
||||
});
|
||||
}
|
||||
|
||||
if (!PermissionService.IsAllowed(role, EntityType.Benchmark, PermissionAction.Create, isMine: true))
|
||||
{
|
||||
return BadRequest(new ResponseResult<StudentBenchmarkItem>
|
||||
{
|
||||
Success = false,
|
||||
Message = "Unable to create benchmark. - Permission Matrix"
|
||||
});
|
||||
}
|
||||
|
||||
var created = await _studentRepository.InsertBenchmarkAsync(dto.GoalId, userId, dto);
|
||||
if (created is null)
|
||||
{
|
||||
return BadRequest(new ResponseResult<StudentBenchmarkItem>
|
||||
{
|
||||
Success = false,
|
||||
Message = "Unable to create benchmark."
|
||||
});
|
||||
}
|
||||
|
||||
return StatusCode(StatusCodes.Status201Created, new ResponseResult<StudentBenchmarkItem>
|
||||
{
|
||||
Success = true,
|
||||
Message = "Benchmark created successfully.",
|
||||
Data = created
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPut("{idStudent:guid}/benchmarks/{idBenchmark:guid}")]
|
||||
[Authorize(Roles = $"{UserRoles.Teacher}")]
|
||||
[ProducesResponseType(typeof(ResponseResult<object>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ResponseResult<object>), StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<ResponseResult<object>>> UpdateBenchmark(Guid idStudent, Guid idBenchmark, [FromBody] UpdateBenchmarkDto dto)
|
||||
{
|
||||
var (userId, email, programId, role, error) = GetProgramUserFromClaims();
|
||||
if (error is not null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
var students = await _studentRepository.GetMyStudentsAsync(userId, programId, role);
|
||||
|
||||
if (!students.Select(s => s.StudentId).Contains(idStudent))
|
||||
{
|
||||
return NotFound(new ResponseResult<object>
|
||||
{
|
||||
Success = false,
|
||||
Message = "Student not found."
|
||||
});
|
||||
}
|
||||
|
||||
var updated = await _studentRepository.UpdateBenchmarkAsync(idBenchmark, dto.Benchmark);
|
||||
|
||||
return Ok(new ResponseResult<object>
|
||||
{
|
||||
Success = true,
|
||||
Message = updated ? "Changes applied successfully." : "No changes were applied."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace WinStudentGoalTracker.DataAccess;
|
||||
|
||||
public class CreateBenchmarkDto
|
||||
{
|
||||
public Guid GoalId { get; set; }
|
||||
public string Benchmark { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace WinStudentGoalTracker.DataAccess;
|
||||
|
||||
public class UpdateBenchmarkDto
|
||||
{
|
||||
public string Benchmark { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace WinStudentGoalTracker.DataAccess;
|
||||
|
||||
public class UpdateGoalDto
|
||||
{
|
||||
public string? Title { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? Category { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace WinStudentGoalTracker.DataAccess;
|
||||
|
||||
public class dbStudentBenchmarkRow
|
||||
{
|
||||
public string? StudentIdentifier { get; set; }
|
||||
public required Guid BenchmarkId { get; set; }
|
||||
public required Guid GoalId { get; set; }
|
||||
public string? GoalTitle { get; set; }
|
||||
public string? Benchmark { get; set; }
|
||||
public string? CreatedByName { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
}
|
||||
@@ -9,4 +9,5 @@ public class dbStudentGoalRow
|
||||
public string? Description { get; set; }
|
||||
public string? Category { get; set; }
|
||||
public int ProgressEventCount { get; set; }
|
||||
public int BenchmarkCount { get; set; }
|
||||
}
|
||||
|
||||
@@ -194,9 +194,119 @@ public class StudentRepository
|
||||
Title = r.Title,
|
||||
Description = r.Description,
|
||||
Category = r.Category,
|
||||
ProgressEventCount = r.ProgressEventCount
|
||||
ProgressEventCount = r.ProgressEventCount,
|
||||
BenchmarkCount = r.BenchmarkCount
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
// *****************************************************************
|
||||
// Updates a goal's title, description, and category.
|
||||
// *****************************************************************
|
||||
public async Task<bool> UpdateGoalAsync(Guid goalId, UpdateGoalDto dto)
|
||||
{
|
||||
using var db = Connection;
|
||||
var rowsAffected = await db.ExecuteScalarAsync<int>(
|
||||
"sp_Goal_Update",
|
||||
new
|
||||
{
|
||||
p_id_goal = goalId.ToString(),
|
||||
p_id_goal_parent = (string?)null,
|
||||
p_id_student = (string?)null,
|
||||
p_id_user_created = (string?)null,
|
||||
p_title = dto.Title,
|
||||
p_description = dto.Description,
|
||||
p_category = dto.Category
|
||||
},
|
||||
commandType: CommandType.StoredProcedure);
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
|
||||
// *****************************************************************
|
||||
// Returns all benchmarks for a student, grouped under a summary
|
||||
// with the student identifier. Returns null if student not found.
|
||||
// *****************************************************************
|
||||
public async Task<StudentBenchmarkSummary?> GetBenchmarkSummaryAsync(Guid idStudent)
|
||||
{
|
||||
using var db = Connection;
|
||||
var rows = await db.QueryAsync<dbStudentBenchmarkRow>(
|
||||
"sp_Benchmark_GetByStudentId",
|
||||
new { p_id_student = idStudent.ToString() },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
var list = rows.ToList();
|
||||
if (list.Count == 0)
|
||||
{
|
||||
var student = await GetByIdAsync(idStudent);
|
||||
if (student is null) return null;
|
||||
|
||||
return new StudentBenchmarkSummary
|
||||
{
|
||||
StudentIdentifier = student.Identifier,
|
||||
Benchmarks = []
|
||||
};
|
||||
}
|
||||
|
||||
return new StudentBenchmarkSummary
|
||||
{
|
||||
StudentIdentifier = list[0].StudentIdentifier,
|
||||
Benchmarks = list.Select(r => new StudentBenchmarkItem
|
||||
{
|
||||
BenchmarkId = r.BenchmarkId,
|
||||
GoalId = r.GoalId,
|
||||
GoalTitle = r.GoalTitle,
|
||||
Benchmark = r.Benchmark,
|
||||
CreatedByName = r.CreatedByName,
|
||||
CreatedAt = r.CreatedAt,
|
||||
UpdatedAt = r.UpdatedAt
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
// *****************************************************************
|
||||
// Inserts a new benchmark and returns the created benchmark item.
|
||||
// *****************************************************************
|
||||
public async Task<StudentBenchmarkItem?> InsertBenchmarkAsync(Guid goalId, Guid userId, CreateBenchmarkDto dto)
|
||||
{
|
||||
var newId = Guid.NewGuid();
|
||||
using var db = Connection;
|
||||
var row = await db.QuerySingleOrDefaultAsync(
|
||||
"sp_Benchmark_Insert",
|
||||
new
|
||||
{
|
||||
p_id_benchmark = newId.ToString(),
|
||||
p_id_goal = goalId.ToString(),
|
||||
p_id_user_created = userId.ToString(),
|
||||
p_benchmark = dto.Benchmark
|
||||
},
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
||||
if (row is null) return null;
|
||||
|
||||
return new StudentBenchmarkItem
|
||||
{
|
||||
BenchmarkId = newId,
|
||||
GoalId = goalId,
|
||||
Benchmark = dto.Benchmark,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
// *****************************************************************
|
||||
// Updates a benchmark's text and returns whether rows were affected.
|
||||
// *****************************************************************
|
||||
public async Task<bool> UpdateBenchmarkAsync(Guid benchmarkId, string benchmarkText)
|
||||
{
|
||||
using var db = Connection;
|
||||
var rowsAffected = await db.ExecuteScalarAsync<int>(
|
||||
"sp_Benchmark_Update",
|
||||
new
|
||||
{
|
||||
p_id_benchmark = benchmarkId.ToString(),
|
||||
p_benchmark = benchmarkText
|
||||
},
|
||||
commandType: CommandType.StoredProcedure);
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace WinStudentGoalTracker.Models;
|
||||
|
||||
public class StudentBenchmarkItem
|
||||
{
|
||||
public Guid BenchmarkId { get; set; }
|
||||
public Guid GoalId { get; set; }
|
||||
public string? GoalTitle { get; set; }
|
||||
public string? Benchmark { get; set; }
|
||||
public string? CreatedByName { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace WinStudentGoalTracker.Models;
|
||||
|
||||
public class StudentBenchmarkSummary
|
||||
{
|
||||
public string? StudentIdentifier { get; set; }
|
||||
public List<StudentBenchmarkItem> Benchmarks { get; set; } = [];
|
||||
}
|
||||
@@ -8,4 +8,5 @@ public class StudentGoalItem
|
||||
public string? Description { get; set; }
|
||||
public string? Category { get; set; }
|
||||
public int ProgressEventCount { get; set; }
|
||||
public int BenchmarkCount { get; set; }
|
||||
}
|
||||
|
||||
@@ -8,10 +8,11 @@ public static class EntityType
|
||||
public const string Student = "student";
|
||||
public const string Goal = "goal";
|
||||
public const string ProgressEvent = "progress_event";
|
||||
public const string Benchmark = "benchmark";
|
||||
|
||||
public static string? TryParse(string value) =>
|
||||
All.Contains(value) ? value : null;
|
||||
|
||||
public static readonly IReadOnlyList<string> All =
|
||||
[SchoolDistrict, Program, User, Student, Goal, ProgressEvent];
|
||||
[SchoolDistrict, Program, User, Student, Goal, ProgressEvent, Benchmark];
|
||||
}
|
||||
|
||||
@@ -60,6 +60,13 @@ public static class PermissionMatrix
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
[EntityType.Benchmark] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
},
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
@@ -109,6 +116,13 @@ public static class PermissionMatrix
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
[EntityType.Benchmark] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
},
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
@@ -158,6 +172,13 @@ public static class PermissionMatrix
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = MineOnly,
|
||||
},
|
||||
[EntityType.Benchmark] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = MineOnly,
|
||||
},
|
||||
},
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
@@ -207,6 +228,13 @@ public static class PermissionMatrix
|
||||
[PermissionAction.Update] = MineOnly,
|
||||
[PermissionAction.Delete] = MineOnly,
|
||||
},
|
||||
[EntityType.Benchmark] = new()
|
||||
{
|
||||
[PermissionAction.Create] = MineOnly,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = MineOnly,
|
||||
[PermissionAction.Delete] = MineOnly,
|
||||
},
|
||||
},
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
@@ -256,6 +284,13 @@ public static class PermissionMatrix
|
||||
[PermissionAction.Update] = MineOnly,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
[EntityType.Benchmark] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Deny,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = Deny,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user