mirror of
https://github.com/opelly27/WinStudentGoalTracker.git
synced 2026-05-20 01:47:41 +00:00
latest
This commit is contained in:
@@ -53,6 +53,7 @@ builder.Services.AddAuthentication(options =>
|
||||
builder.Services.AddAuthorization();
|
||||
|
||||
builder.Services.AddScoped<TokenService>();
|
||||
builder.Services.AddScoped<PermissionService>();
|
||||
|
||||
builder.Services.AddHttpClient<TranscriptionService>(client =>
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WinStudentGoalTracker.Models;
|
||||
using WinStudentGoalTracker.BaseClasses;
|
||||
@@ -12,14 +13,11 @@ public class StudentController : BaseController
|
||||
private readonly StudentRepository _studentRepository = new();
|
||||
|
||||
|
||||
// TODO refactor this stored procedure
|
||||
// to getmystudents
|
||||
// This required auth system to be set up first
|
||||
[HttpGet]
|
||||
[HttpGet("my")]
|
||||
[Authorize(Roles = $"{UserRoles.Teacher},{UserRoles.Paraeducator},{UserRoles.ProgramAdmin}")]
|
||||
[ProducesResponseType(typeof(ResponseResult<IEnumerable<StudentResponse>>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ResponseResult<IEnumerable<StudentResponse>>>> GetAll()
|
||||
public async Task<ActionResult<ResponseResult<IEnumerable<StudentResponse>>>> GetMyStudents()
|
||||
{
|
||||
|
||||
var (userId, email, programId, role, error) = GetProgramUserFromClaims();
|
||||
|
||||
if (error is not null)
|
||||
@@ -27,7 +25,34 @@ public class StudentController : BaseController
|
||||
return error;
|
||||
}
|
||||
|
||||
var students = await _studentRepository.GetAllAsync();
|
||||
var students = await _studentRepository.GetMyStudentsAsync(userId, programId, role);
|
||||
var response = students.Select(StudentResponse.FromDatabaseModel);
|
||||
|
||||
return Ok(new ResponseResult<IEnumerable<StudentResponse>>
|
||||
{
|
||||
Success = true,
|
||||
Data = response
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// TODO refactor with database changes to ensure
|
||||
// users who are a district admin are actually associated with a district, and
|
||||
// then this endpoint should validate that the requested program is part of the district
|
||||
// Once that is in place, then district admins will be allowed to call this function.
|
||||
[HttpGet("program/{idProgram:guid}")]
|
||||
[Authorize(Roles = $"{UserRoles.SuperAdmin}")]
|
||||
[ProducesResponseType(typeof(ResponseResult<IEnumerable<StudentResponse>>), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<ResponseResult<IEnumerable<StudentResponse>>>> GetStudentsForProgram(Guid idProgram)
|
||||
{
|
||||
var (userId, email, programId, role, error) = GetProgramUserFromClaims();
|
||||
|
||||
if (error is not null)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
var students = await _studentRepository.GetStudentsByProgramAsync(idProgram);
|
||||
var response = students.Select(StudentResponse.FromDatabaseModel);
|
||||
|
||||
return Ok(new ResponseResult<IEnumerable<StudentResponse>>
|
||||
@@ -38,12 +63,21 @@ public class StudentController : BaseController
|
||||
}
|
||||
|
||||
[HttpGet("{idStudent:guid}")]
|
||||
[Authorize(Roles = $"{UserRoles.Teacher},{UserRoles.Paraeducator},{UserRoles.ProgramAdmin}")]
|
||||
[ProducesResponseType(typeof(ResponseResult<StudentResponse>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ResponseResult<StudentResponse>), StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<ResponseResult<StudentResponse>>> GetById(Guid idStudent)
|
||||
{
|
||||
var student = await _studentRepository.GetByIdAsync(idStudent);
|
||||
if (student is null)
|
||||
|
||||
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.IdStudent).Contains(idStudent))
|
||||
{
|
||||
return NotFound(new ResponseResult<StudentResponse>
|
||||
{
|
||||
@@ -51,6 +85,8 @@ public class StudentController : BaseController
|
||||
Message = "Student not found."
|
||||
});
|
||||
}
|
||||
|
||||
var student = students.Single(s => s.IdStudent == idStudent);
|
||||
|
||||
return Ok(new ResponseResult<StudentResponse>
|
||||
{
|
||||
@@ -60,21 +96,20 @@ public class StudentController : BaseController
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Roles = $"{UserRoles.Teacher}")]
|
||||
[ProducesResponseType(typeof(ResponseResult<StudentResponse>), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ResponseResult<StudentResponse>), StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<ResponseResult<StudentResponse>>> Create([FromBody] CreateStudentDto request)
|
||||
public async Task<ActionResult<ResponseResult<StudentResponse>>> Create([FromBody] CreateStudentDto newStudentData)
|
||||
{
|
||||
var existing = await _studentRepository.GetByIdAsync(request.IdStudent);
|
||||
if (existing is not null)
|
||||
var (userId, email, programId, role, error) = GetProgramUserFromClaims();
|
||||
|
||||
if (error is not null)
|
||||
{
|
||||
return BadRequest(new ResponseResult<StudentResponse>
|
||||
{
|
||||
Success = false,
|
||||
Message = $"Student with id {request.IdStudent} already exists."
|
||||
});
|
||||
return error;
|
||||
}
|
||||
|
||||
var created = await _studentRepository.InsertAsync(request);
|
||||
var newStudentId = Guid.NewGuid();
|
||||
var created = await _studentRepository.InsertAsync(newStudentData, newStudentId, programId, userId);
|
||||
if (created is null)
|
||||
{
|
||||
return BadRequest(new ResponseResult<StudentResponse>
|
||||
@@ -93,12 +128,20 @@ public class StudentController : BaseController
|
||||
}
|
||||
|
||||
[HttpPut("{idStudent:guid}")]
|
||||
[Authorize(Roles = $"{UserRoles.Teacher}")]
|
||||
[ProducesResponseType(typeof(ResponseResult<StudentResponse>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ResponseResult<StudentResponse>), StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<ResponseResult<StudentResponse>>> Update(Guid idStudent, [FromBody] UpdateStudentDto request)
|
||||
{
|
||||
var existing = await _studentRepository.GetByIdAsync(idStudent);
|
||||
if (existing is null)
|
||||
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.IdStudent).Contains(idStudent))
|
||||
{
|
||||
return NotFound(new ResponseResult<StudentResponse>
|
||||
{
|
||||
@@ -127,10 +170,28 @@ public class StudentController : BaseController
|
||||
}
|
||||
|
||||
[HttpDelete("{idStudent:guid}")]
|
||||
[Authorize(Roles = $"{UserRoles.Teacher}")]
|
||||
[ProducesResponseType(typeof(ResponseResult<object>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ResponseResult<object>), StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<ResponseResult<object>>> Delete(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.IdStudent).Contains(idStudent))
|
||||
{
|
||||
return NotFound(new ResponseResult<StudentResponse>
|
||||
{
|
||||
Success = false,
|
||||
Message = "Student not found."
|
||||
});
|
||||
}
|
||||
|
||||
var deleted = await _studentRepository.DeleteAsync(idStudent);
|
||||
if (!deleted)
|
||||
{
|
||||
|
||||
@@ -2,8 +2,6 @@ namespace WinStudentGoalTracker.DataAccess;
|
||||
|
||||
public class CreateStudentDto
|
||||
{
|
||||
public required Guid IdStudent { get; set; }
|
||||
public Guid? IdProgram { get; set; }
|
||||
public string? Identifier { get; set; }
|
||||
public int? ProgramYear { get; set; }
|
||||
public DateTime? EnrollmentDate { get; set; }
|
||||
|
||||
@@ -2,7 +2,6 @@ namespace WinStudentGoalTracker.DataAccess;
|
||||
|
||||
public class UpdateStudentDto
|
||||
{
|
||||
public Guid? IdProgram { get; set; }
|
||||
public string? Identifier { get; set; }
|
||||
public int? ProgramYear { get; set; }
|
||||
public DateTime? EnrollmentDate { get; set; }
|
||||
|
||||
@@ -4,6 +4,7 @@ public class dbStudent
|
||||
{
|
||||
public required Guid IdStudent { get; set; }
|
||||
public Guid? IdProgram { get; set; }
|
||||
public Guid PrimaryTeacherId { get; set; }
|
||||
public string? Identifier { get; set; }
|
||||
public int? ProgramYear { get; set; }
|
||||
public DateTime? EnrollmentDate { get; set; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Data;
|
||||
using Dapper;
|
||||
using MySql.Data.MySqlClient;
|
||||
using WinStudentGoalTracker.Models;
|
||||
|
||||
namespace WinStudentGoalTracker.DataAccess;
|
||||
|
||||
@@ -8,11 +9,33 @@ public class StudentRepository
|
||||
{
|
||||
private IDbConnection Connection => new MySqlConnection(DatabaseManager.ConnectionString);
|
||||
|
||||
public async Task<IEnumerable<dbStudent>> GetAllAsync()
|
||||
public async Task<IEnumerable<dbStudent>> GetMyStudentsAsync(Guid userId, Guid programId, string role)
|
||||
{
|
||||
return role switch
|
||||
{
|
||||
UserRoles.Teacher or UserRoles.ProgramAdmin =>
|
||||
await GetStudentsByProgramAsync(programId),
|
||||
UserRoles.Paraeducator =>
|
||||
await GetAssignedStudentsAsync(userId, programId),
|
||||
_ => Enumerable.Empty<dbStudent>()
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<dbStudent>> GetStudentsByProgramAsync(Guid programId)
|
||||
{
|
||||
using var db = Connection;
|
||||
return await db.QueryAsync<dbStudent>(
|
||||
"sp_Student_GetAll",
|
||||
"sp_Student_GetByProgram",
|
||||
new { p_id_program = programId.ToString() },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<dbStudent>> GetAssignedStudentsAsync(Guid userId, Guid programId)
|
||||
{
|
||||
using var db = Connection;
|
||||
return await db.QueryAsync<dbStudent>(
|
||||
"sp_Student_GetByUserAndProgram",
|
||||
new { p_id_user = userId.ToString(), p_id_program = programId.ToString() },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
@@ -25,15 +48,16 @@ public class StudentRepository
|
||||
commandType: CommandType.StoredProcedure);
|
||||
}
|
||||
|
||||
public async Task<dbStudent?> InsertAsync(CreateStudentDto dto)
|
||||
public async Task<dbStudent?> InsertAsync(CreateStudentDto dto, Guid newStudentGuid, Guid programId, Guid userId)
|
||||
{
|
||||
using var db = Connection;
|
||||
return await db.QuerySingleOrDefaultAsync<dbStudent>(
|
||||
"sp_Student_Insert",
|
||||
new
|
||||
{
|
||||
p_id_student = dto.IdStudent.ToString(),
|
||||
p_id_program = dto.IdProgram?.ToString(),
|
||||
p_id_student = newStudentGuid.ToString(),
|
||||
p_id_program = programId,
|
||||
p_id_user = userId.ToString(),
|
||||
p_identifier = dto.Identifier,
|
||||
p_program_year = dto.ProgramYear,
|
||||
p_enrollment_date = dto.EnrollmentDate,
|
||||
@@ -50,7 +74,6 @@ public class StudentRepository
|
||||
new
|
||||
{
|
||||
p_id_student = idStudent.ToString(),
|
||||
p_id_program = dto.IdProgram?.ToString(),
|
||||
p_identifier = dto.Identifier,
|
||||
p_program_year = dto.ProgramYear,
|
||||
p_enrollment_date = dto.EnrollmentDate,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace WinStudentGoalTracker.Models;
|
||||
|
||||
public static class EntityType
|
||||
{
|
||||
public const string SchoolDistrict = "school_district";
|
||||
public const string Program = "program";
|
||||
public const string User = "user";
|
||||
public const string Student = "student";
|
||||
public const string Goal = "goal";
|
||||
public const string ProgressEvent = "progress_event";
|
||||
|
||||
public static string? TryParse(string value) =>
|
||||
All.Contains(value) ? value : null;
|
||||
|
||||
public static readonly IReadOnlyList<string> All =
|
||||
[SchoolDistrict, Program, User, Student, Goal, ProgressEvent];
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace WinStudentGoalTracker.Models;
|
||||
|
||||
public static class PermissionAction
|
||||
{
|
||||
public const string Create = "create";
|
||||
public const string Read = "read";
|
||||
public const string Update = "update";
|
||||
public const string Delete = "delete";
|
||||
|
||||
public static string? TryParse(string value) =>
|
||||
All.Contains(value) ? value : null;
|
||||
|
||||
public static readonly IReadOnlyList<string> All =
|
||||
[Create, Read, Update, Delete];
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
using WinStudentGoalTracker.Models;
|
||||
|
||||
namespace WinStudentGoalTracker.Services;
|
||||
|
||||
public readonly record struct PermissionRule(bool Mine, bool Others);
|
||||
|
||||
public static class PermissionMatrix
|
||||
{
|
||||
private static readonly PermissionRule Allow = new(true, true);
|
||||
private static readonly PermissionRule MineOnly = new(true, false);
|
||||
private static readonly PermissionRule Deny = new(false, false);
|
||||
|
||||
// _rules[role][entity][action] → PermissionRule
|
||||
private static readonly Dictionary<string, Dictionary<string, Dictionary<string, PermissionRule>>> _rules = new()
|
||||
{
|
||||
// ──────────────────────────────────────────────
|
||||
// Super Admin — full access to everything
|
||||
// ──────────────────────────────────────────────
|
||||
[UserRoles.SuperAdmin] = new()
|
||||
{
|
||||
[EntityType.SchoolDistrict] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
[EntityType.Program] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
[EntityType.User] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
[EntityType.Student] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
[EntityType.Goal] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
[EntityType.ProgressEvent] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
},
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// District Admin
|
||||
// ──────────────────────────────────────────────
|
||||
[UserRoles.DistrictAdmin] = new()
|
||||
{
|
||||
[EntityType.SchoolDistrict] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Deny,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = MineOnly,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
[EntityType.Program] = new()
|
||||
{
|
||||
[PermissionAction.Create] = MineOnly,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = MineOnly,
|
||||
},
|
||||
[EntityType.User] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
[EntityType.Student] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
[EntityType.Goal] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
[EntityType.ProgressEvent] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = Allow,
|
||||
},
|
||||
},
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Program Admin
|
||||
// ──────────────────────────────────────────────
|
||||
[UserRoles.ProgramAdmin] = new()
|
||||
{
|
||||
[EntityType.SchoolDistrict] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Deny,
|
||||
[PermissionAction.Read] = Deny,
|
||||
[PermissionAction.Update] = Deny,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
[EntityType.Program] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Deny,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = MineOnly,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
[EntityType.User] = new()
|
||||
{
|
||||
[PermissionAction.Create] = MineOnly,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = MineOnly,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
[EntityType.Student] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = MineOnly,
|
||||
},
|
||||
[EntityType.Goal] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = MineOnly,
|
||||
},
|
||||
[EntityType.ProgressEvent] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Allow,
|
||||
[PermissionAction.Read] = Allow,
|
||||
[PermissionAction.Update] = Allow,
|
||||
[PermissionAction.Delete] = MineOnly,
|
||||
},
|
||||
},
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Teacher
|
||||
// ──────────────────────────────────────────────
|
||||
[UserRoles.Teacher] = new()
|
||||
{
|
||||
[EntityType.SchoolDistrict] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Deny,
|
||||
[PermissionAction.Read] = Deny,
|
||||
[PermissionAction.Update] = Deny,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
[EntityType.Program] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Deny,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = Deny,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
[EntityType.User] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Deny,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = MineOnly,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
[EntityType.Student] = new()
|
||||
{
|
||||
[PermissionAction.Create] = MineOnly,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = MineOnly,
|
||||
[PermissionAction.Delete] = MineOnly,
|
||||
},
|
||||
[EntityType.Goal] = new()
|
||||
{
|
||||
[PermissionAction.Create] = MineOnly,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = MineOnly,
|
||||
[PermissionAction.Delete] = MineOnly,
|
||||
},
|
||||
[EntityType.ProgressEvent] = new()
|
||||
{
|
||||
[PermissionAction.Create] = MineOnly,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = MineOnly,
|
||||
[PermissionAction.Delete] = MineOnly,
|
||||
},
|
||||
},
|
||||
|
||||
// ──────────────────────────────────────────────
|
||||
// Paraeducator
|
||||
// ──────────────────────────────────────────────
|
||||
[UserRoles.Paraeducator] = new()
|
||||
{
|
||||
[EntityType.SchoolDistrict] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Deny,
|
||||
[PermissionAction.Read] = Deny,
|
||||
[PermissionAction.Update] = Deny,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
[EntityType.Program] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Deny,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = Deny,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
[EntityType.User] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Deny,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = MineOnly,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
[EntityType.Student] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Deny,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = Deny,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
[EntityType.Goal] = new()
|
||||
{
|
||||
[PermissionAction.Create] = Deny,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = Deny,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
[EntityType.ProgressEvent] = new()
|
||||
{
|
||||
[PermissionAction.Create] = MineOnly,
|
||||
[PermissionAction.Read] = MineOnly,
|
||||
[PermissionAction.Update] = MineOnly,
|
||||
[PermissionAction.Delete] = Deny,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
public static PermissionRule? GetRule(string role, string entity, string action)
|
||||
{
|
||||
if (_rules.TryGetValue(role, out var entities)
|
||||
&& entities.TryGetValue(entity, out var actions)
|
||||
&& actions.TryGetValue(action, out var rule))
|
||||
{
|
||||
return rule;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using WinStudentGoalTracker.Models;
|
||||
|
||||
namespace WinStudentGoalTracker.Services;
|
||||
|
||||
public class PermissionService
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks whether the given role is allowed to perform the specified action
|
||||
/// on the specified entity, considering ownership.
|
||||
/// </summary>
|
||||
/// <param name="role">The user's role (use UserRoles constants)</param>
|
||||
/// <param name="entity">The entity being acted on (use EntityType constants)</param>
|
||||
/// <param name="action">The action being performed (use PermissionAction constants)</param>
|
||||
/// <param name="isMine">Whether the resource belongs to the requesting user.
|
||||
/// For Create actions this parameter is ignored.</param>
|
||||
/// <returns>True if the action is permitted, false otherwise.</returns>
|
||||
public bool IsAllowed(string role, string entity, string action, bool isMine = true)
|
||||
{
|
||||
var rule = PermissionMatrix.GetRule(role, entity, action);
|
||||
|
||||
if (rule is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Create has no ownership concept — use the Mine field as a general "can create" flag
|
||||
if (action == PermissionAction.Create)
|
||||
{
|
||||
return rule.Value.Mine;
|
||||
}
|
||||
|
||||
|
||||
return isMine ? rule.Value.Mine : rule.Value.Others;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
DELIMITER ;;
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `sp_Student_GetByProgram`(
|
||||
IN p_id_program CHAR(36)
|
||||
)
|
||||
BEGIN
|
||||
SELECT
|
||||
id_student,
|
||||
id_program,
|
||||
identifier,
|
||||
program_year,
|
||||
enrollment_date,
|
||||
expected_grad,
|
||||
created_at
|
||||
FROM student
|
||||
WHERE id_program = p_id_program
|
||||
ORDER BY id_student;
|
||||
END;;
|
||||
DELIMITER ;
|
||||
@@ -0,0 +1,21 @@
|
||||
DELIMITER ;;
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `sp_Student_GetByUserAndProgram`(
|
||||
IN p_id_user CHAR(36),
|
||||
IN p_id_program CHAR(36)
|
||||
)
|
||||
BEGIN
|
||||
SELECT
|
||||
s.id_student,
|
||||
s.id_program,
|
||||
s.identifier,
|
||||
s.program_year,
|
||||
s.enrollment_date,
|
||||
s.expected_grad,
|
||||
s.created_at
|
||||
FROM student s
|
||||
JOIN user_student us ON us.id_student = s.id_student
|
||||
WHERE us.id_user = p_id_user
|
||||
AND s.id_program = p_id_program
|
||||
ORDER BY s.id_student;
|
||||
END;;
|
||||
DELIMITER ;
|
||||
@@ -28,6 +28,22 @@ BEGIN
|
||||
p_expected_grad,
|
||||
UTC_TIMESTAMP()
|
||||
);
|
||||
|
||||
INSERT INTO user_student
|
||||
(
|
||||
id_user_student,
|
||||
id_user,
|
||||
id_student,
|
||||
is_primary
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
UUID(),
|
||||
p_id_user,
|
||||
p_id_student,
|
||||
1
|
||||
);
|
||||
|
||||
SELECT
|
||||
id_student,
|
||||
id_program,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
DELIMITER ;;
|
||||
CREATE DEFINER=`root`@`%` PROCEDURE `sp_Student_Update`(
|
||||
IN p_id_student CHAR(36),
|
||||
IN p_id_program CHAR(36),
|
||||
IN p_identifier VARCHAR(50),
|
||||
IN p_program_year INT,
|
||||
IN p_enrollment_date DATE,
|
||||
@@ -10,7 +9,6 @@ CREATE DEFINER=`root`@`%` PROCEDURE `sp_Student_Update`(
|
||||
BEGIN
|
||||
UPDATE student
|
||||
SET
|
||||
id_program = COALESCE(p_id_program, id_program),
|
||||
identifier = COALESCE(p_identifier, identifier),
|
||||
program_year = COALESCE(p_program_year, program_year),
|
||||
enrollment_date = COALESCE(p_enrollment_date, enrollment_date),
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
CREATE TABLE `permission` (
|
||||
`id_permission` char(36) NOT NULL,
|
||||
`name` varchar(100) DEFAULT NULL,
|
||||
`description` text,
|
||||
`resource` varchar(100) DEFAULT NULL,
|
||||
`action` varchar(50) DEFAULT NULL,
|
||||
`scope` varchar(50) DEFAULT NULL,
|
||||
PRIMARY KEY (`id_permission`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
@@ -1,10 +0,0 @@
|
||||
CREATE TABLE `role_permission` (
|
||||
`id_role_permission` char(36) NOT NULL,
|
||||
`id_role` char(36) DEFAULT NULL,
|
||||
`id_permission` char(36) DEFAULT NULL,
|
||||
PRIMARY KEY (`id_role_permission`),
|
||||
KEY `role_permission_ibfk_1` (`id_role`),
|
||||
KEY `role_permission_ibfk_2` (`id_permission`),
|
||||
CONSTRAINT `role_permission_ibfk_1` FOREIGN KEY (`id_role`) REFERENCES `role` (`id_role`),
|
||||
CONSTRAINT `role_permission_ibfk_2` FOREIGN KEY (`id_permission`) REFERENCES `permission` (`id_permission`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
Reference in New Issue
Block a user