mirror of
https://github.com/opelly27/WinStudentGoalTracker.git
synced 2026-05-20 13:27:35 +00:00
Prototype illustrative role assignments project
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
// =============================================================================
|
||||
// DummyAssignmentRepository.cs
|
||||
// =============================================================================
|
||||
// This is the HARDCODED version of the assignment repository.
|
||||
// It simulates what the database would return, without needing a real database.
|
||||
//
|
||||
// THE DEMO DATA:
|
||||
// ┌─────────┬───────────────┬────────────┬───────────────────┐
|
||||
// │ User ID │ Name │ Student ID │ Assignment Type │
|
||||
// ├─────────┼───────────────┼────────────┼───────────────────┤
|
||||
// │ 1 │ Ms. Rivera │ 101 │ PrimaryTeacher │
|
||||
// │ 1 │ Ms. Rivera │ 102 │ PrimaryTeacher │
|
||||
// │ 2 │ Mr. Daniels │ 101 │ Paraeducator │
|
||||
// │ 3 │ Dr. Patel │ 101 │ Supervisor │
|
||||
// │ 3 │ Dr. Patel │ 102 │ Supervisor │
|
||||
// │ 3 │ Dr. Patel │ 103 │ Supervisor │
|
||||
// │ 3 │ Dr. Patel │ 104 │ Supervisor │
|
||||
// └─────────┴───────────────┴────────────┴───────────────────┘
|
||||
//
|
||||
// Notice: Student 103 and 104 are NOT assigned to User 1 (Ms. Rivera) or
|
||||
// User 2 (Mr. Daniels). If those users try to access those students, the
|
||||
// authorization handler will get null from GetActiveAssignment and deny access.
|
||||
//
|
||||
// Dr. Patel (Supervisor) has Supervisor-type assignments to ALL students,
|
||||
// which grants read-only access everywhere.
|
||||
// =============================================================================
|
||||
|
||||
using RolesAssignments.Models;
|
||||
|
||||
namespace RolesAssignments.Data;
|
||||
|
||||
public class DummyAssignmentRepository : IAssignmentRepository
|
||||
{
|
||||
// This list simulates the student_assignments table in the database.
|
||||
// Each entry represents one row — one relationship between a user and a student.
|
||||
private static readonly List<StudentAssignment> _assignments = new()
|
||||
{
|
||||
// Ms. Rivera (User 1) is the primary teacher for Students 101 and 102
|
||||
new StudentAssignment
|
||||
{
|
||||
Id = 1, UserId = 1, StudentId = 101,
|
||||
AssignmentType = AssignmentType.PrimaryTeacher,
|
||||
StartDate = new DateTime(2025, 9, 1), EndDate = null, IsActive = true
|
||||
},
|
||||
new StudentAssignment
|
||||
{
|
||||
Id = 2, UserId = 1, StudentId = 102,
|
||||
AssignmentType = AssignmentType.PrimaryTeacher,
|
||||
StartDate = new DateTime(2025, 9, 1), EndDate = null, IsActive = true
|
||||
},
|
||||
|
||||
// Mr. Daniels (User 2) is a paraeducator assigned to Student 101
|
||||
new StudentAssignment
|
||||
{
|
||||
Id = 3, UserId = 2, StudentId = 101,
|
||||
AssignmentType = AssignmentType.Paraeducator,
|
||||
StartDate = new DateTime(2025, 9, 1), EndDate = null, IsActive = true
|
||||
},
|
||||
|
||||
// Dr. Patel (User 3) is a supervisor for ALL students
|
||||
new StudentAssignment
|
||||
{
|
||||
Id = 4, UserId = 3, StudentId = 101,
|
||||
AssignmentType = AssignmentType.Supervisor,
|
||||
StartDate = new DateTime(2025, 9, 1), EndDate = null, IsActive = true
|
||||
},
|
||||
new StudentAssignment
|
||||
{
|
||||
Id = 5, UserId = 3, StudentId = 102,
|
||||
AssignmentType = AssignmentType.Supervisor,
|
||||
StartDate = new DateTime(2025, 9, 1), EndDate = null, IsActive = true
|
||||
},
|
||||
new StudentAssignment
|
||||
{
|
||||
Id = 6, UserId = 3, StudentId = 103,
|
||||
AssignmentType = AssignmentType.Supervisor,
|
||||
StartDate = new DateTime(2025, 9, 1), EndDate = null, IsActive = true
|
||||
},
|
||||
new StudentAssignment
|
||||
{
|
||||
Id = 7, UserId = 3, StudentId = 104,
|
||||
AssignmentType = AssignmentType.Supervisor,
|
||||
StartDate = new DateTime(2025, 9, 1), EndDate = null, IsActive = true
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Finds an active assignment between the given user and student.
|
||||
/// Returns null if no active assignment exists.
|
||||
/// </summary>
|
||||
public Task<StudentAssignment?> GetActiveAssignment(int userId, int studentId)
|
||||
{
|
||||
// This LINQ query does in-memory what the SQL query would do in a real database.
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var assignment = _assignments.FirstOrDefault(a =>
|
||||
a.UserId == userId
|
||||
&& a.StudentId == studentId
|
||||
&& a.IsActive
|
||||
&& a.StartDate <= now
|
||||
&& (a.EndDate == null || a.EndDate >= now));
|
||||
|
||||
// ==========================================
|
||||
// REAL DATABASE VERSION (commented out):
|
||||
// ==========================================
|
||||
// In production, this method would use Dapper to query MySQL:
|
||||
//
|
||||
// public async Task<StudentAssignment?> GetActiveAssignment(int userId, int studentId)
|
||||
// {
|
||||
// return await _db.QueryFirstOrDefaultAsync<StudentAssignment>(
|
||||
// @"SELECT id, user_id AS UserId, student_id AS StudentId,
|
||||
// assignment_type AS AssignmentType,
|
||||
// start_date AS StartDate, end_date AS EndDate,
|
||||
// is_active AS IsActive
|
||||
// FROM student_assignments
|
||||
// WHERE user_id = @UserId
|
||||
// AND student_id = @StudentId
|
||||
// AND is_active = TRUE
|
||||
// AND start_date <= CURDATE()
|
||||
// AND (end_date IS NULL OR end_date >= CURDATE())
|
||||
// LIMIT 1",
|
||||
// new { UserId = userId, StudentId = studentId });
|
||||
// }
|
||||
|
||||
return Task.FromResult(assignment);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// =============================================================================
|
||||
// DummyGoalRepository.cs
|
||||
// =============================================================================
|
||||
// Hardcoded goal data. Goals belong to students and can only be created/edited
|
||||
// by the PrimaryTeacher (enforced by the authorization handler, not here).
|
||||
//
|
||||
// This repository is intentionally "dumb" — it just stores and retrieves data.
|
||||
// Authorization is handled elsewhere (in the handlers and controllers).
|
||||
// This separation of concerns is important: the repository doesn't know or
|
||||
// care about permissions. It just does CRUD.
|
||||
// =============================================================================
|
||||
|
||||
using RolesAssignments.Models;
|
||||
|
||||
namespace RolesAssignments.Data;
|
||||
|
||||
public class DummyGoalRepository : IGoalRepository
|
||||
{
|
||||
// Simulates the goals table. We use a static list so data persists
|
||||
// across requests during a single run of the app (but resets on restart).
|
||||
private static readonly List<Goal> _goals = new()
|
||||
{
|
||||
new Goal
|
||||
{
|
||||
Id = 1, StudentId = 101,
|
||||
Title = "Reading Fluency",
|
||||
Description = "Student will read 20 words per minute by end of semester.",
|
||||
CreatedByUserId = 1, CreatedAt = new DateTime(2025, 9, 15)
|
||||
},
|
||||
new Goal
|
||||
{
|
||||
Id = 2, StudentId = 101,
|
||||
Title = "Math Facts",
|
||||
Description = "Student will master addition facts to 10.",
|
||||
CreatedByUserId = 1, CreatedAt = new DateTime(2025, 9, 15)
|
||||
},
|
||||
new Goal
|
||||
{
|
||||
Id = 3, StudentId = 102,
|
||||
Title = "Social Skills",
|
||||
Description = "Student will initiate peer interactions 3 times per day.",
|
||||
CreatedByUserId = 1, CreatedAt = new DateTime(2025, 9, 16)
|
||||
},
|
||||
};
|
||||
|
||||
// Counter for generating new IDs (simulates AUTO_INCREMENT)
|
||||
private static int _nextId = 4;
|
||||
|
||||
public Task<IEnumerable<Goal>> GetByStudentId(int studentId)
|
||||
{
|
||||
var goals = _goals.Where(g => g.StudentId == studentId);
|
||||
|
||||
// ==========================================
|
||||
// REAL DATABASE VERSION (commented out):
|
||||
// ==========================================
|
||||
// return await _db.QueryAsync<Goal>(
|
||||
// @"SELECT id AS Id, student_id AS StudentId, title AS Title,
|
||||
// description AS Description,
|
||||
// created_by_user_id AS CreatedByUserId,
|
||||
// created_at AS CreatedAt
|
||||
// FROM goals
|
||||
// WHERE student_id = @StudentId
|
||||
// ORDER BY created_at",
|
||||
// new { StudentId = studentId });
|
||||
|
||||
return Task.FromResult(goals);
|
||||
}
|
||||
|
||||
public Task<Goal?> GetById(int goalId)
|
||||
{
|
||||
var goal = _goals.FirstOrDefault(g => g.Id == goalId);
|
||||
return Task.FromResult(goal);
|
||||
}
|
||||
|
||||
public Task<int> Create(int studentId, CreateGoalRequest request, int createdByUserId)
|
||||
{
|
||||
var goal = new Goal
|
||||
{
|
||||
Id = _nextId++,
|
||||
StudentId = studentId,
|
||||
Title = request.Title,
|
||||
Description = request.Description,
|
||||
CreatedByUserId = createdByUserId,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
_goals.Add(goal);
|
||||
|
||||
// ==========================================
|
||||
// REAL DATABASE VERSION (commented out):
|
||||
// ==========================================
|
||||
// var goalId = await _db.ExecuteScalarAsync<int>(
|
||||
// @"INSERT INTO goals (student_id, title, description, created_by_user_id, created_at)
|
||||
// VALUES (@StudentId, @Title, @Description, @CreatedByUserId, NOW());
|
||||
// SELECT LAST_INSERT_ID();",
|
||||
// new { StudentId = studentId, request.Title, request.Description, CreatedByUserId = createdByUserId });
|
||||
|
||||
return Task.FromResult(goal.Id);
|
||||
}
|
||||
|
||||
public Task Update(int goalId, UpdateGoalRequest request, int updatedByUserId)
|
||||
{
|
||||
var goal = _goals.FirstOrDefault(g => g.Id == goalId);
|
||||
if (goal is not null)
|
||||
{
|
||||
goal.Title = request.Title;
|
||||
goal.Description = request.Description;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// REAL DATABASE VERSION (commented out):
|
||||
// ==========================================
|
||||
// await _db.ExecuteAsync(
|
||||
// @"UPDATE goals
|
||||
// SET title = @Title, description = @Description,
|
||||
// updated_by_user_id = @UpdatedByUserId, updated_at = NOW()
|
||||
// WHERE id = @GoalId",
|
||||
// new { GoalId = goalId, request.Title, request.Description, UpdatedByUserId = updatedByUserId });
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// =============================================================================
|
||||
// DummyProgressEntryRepository.cs
|
||||
// =============================================================================
|
||||
// Hardcoded progress entry data. This is the entity that demonstrates the
|
||||
// ownership model — entries have a CreatedByUserId, and the authorization
|
||||
// handler uses it to determine who can edit/delete them.
|
||||
//
|
||||
// The seed data includes entries created by different users so you can test:
|
||||
// - Ms. Rivera (User 1) editing any entry for her students → ALLOWED
|
||||
// - Mr. Daniels (User 2) editing his own entry → ALLOWED
|
||||
// - Mr. Daniels (User 2) editing Ms. Rivera's entry → DENIED
|
||||
// =============================================================================
|
||||
|
||||
using RolesAssignments.Models;
|
||||
|
||||
namespace RolesAssignments.Data;
|
||||
|
||||
public class DummyProgressEntryRepository : IProgressEntryRepository
|
||||
{
|
||||
// Simulates the progress_entries table
|
||||
private static readonly List<ProgressEntry> _entries = new()
|
||||
{
|
||||
// Entry created by Ms. Rivera (User 1) for Student 101
|
||||
new ProgressEntry
|
||||
{
|
||||
Id = 1, StudentId = 101,
|
||||
Notes = "Student read 12 words per minute today. Showing improvement.",
|
||||
CreatedByUserId = 1, // Ms. Rivera made this entry
|
||||
CreatedAt = new DateTime(2025, 10, 1),
|
||||
IsDeleted = false
|
||||
},
|
||||
|
||||
// Entry created by Mr. Daniels (User 2) for Student 101
|
||||
new ProgressEntry
|
||||
{
|
||||
Id = 2, StudentId = 101,
|
||||
Notes = "Worked on addition facts during small group. Got 7/10 correct.",
|
||||
CreatedByUserId = 2, // Mr. Daniels made this entry
|
||||
CreatedAt = new DateTime(2025, 10, 2),
|
||||
IsDeleted = false
|
||||
},
|
||||
|
||||
// Another entry by Ms. Rivera for Student 102
|
||||
new ProgressEntry
|
||||
{
|
||||
Id = 3, StudentId = 102,
|
||||
Notes = "Student initiated conversation with peer during recess.",
|
||||
CreatedByUserId = 1, // Ms. Rivera made this entry
|
||||
CreatedAt = new DateTime(2025, 10, 3),
|
||||
IsDeleted = false
|
||||
},
|
||||
};
|
||||
|
||||
private static int _nextId = 4;
|
||||
|
||||
public Task<IEnumerable<ProgressEntry>> GetByStudentId(int studentId)
|
||||
{
|
||||
var entries = _entries.Where(e => e.StudentId == studentId && !e.IsDeleted);
|
||||
|
||||
// ==========================================
|
||||
// REAL DATABASE VERSION (commented out):
|
||||
// ==========================================
|
||||
// return await _db.QueryAsync<ProgressEntry>(
|
||||
// @"SELECT id AS Id, student_id AS StudentId, notes AS Notes,
|
||||
// created_by_user_id AS CreatedByUserId,
|
||||
// created_at AS CreatedAt, is_deleted AS IsDeleted
|
||||
// FROM progress_entries
|
||||
// WHERE student_id = @StudentId AND is_deleted = FALSE
|
||||
// ORDER BY created_at DESC",
|
||||
// new { StudentId = studentId });
|
||||
|
||||
return Task.FromResult(entries);
|
||||
}
|
||||
|
||||
public Task<ProgressEntry?> GetById(int entryId)
|
||||
{
|
||||
var entry = _entries.FirstOrDefault(e => e.Id == entryId && !e.IsDeleted);
|
||||
return Task.FromResult(entry);
|
||||
}
|
||||
|
||||
public Task<int> Create(int studentId, CreateEntryRequest request, int createdByUserId)
|
||||
{
|
||||
var entry = new ProgressEntry
|
||||
{
|
||||
Id = _nextId++,
|
||||
StudentId = studentId,
|
||||
Notes = request.Notes,
|
||||
CreatedByUserId = createdByUserId,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
IsDeleted = false
|
||||
};
|
||||
_entries.Add(entry);
|
||||
|
||||
// ==========================================
|
||||
// REAL DATABASE VERSION (commented out):
|
||||
// ==========================================
|
||||
// var entryId = await _db.ExecuteScalarAsync<int>(
|
||||
// @"INSERT INTO progress_entries (student_id, notes, created_by_user_id, created_at, is_deleted)
|
||||
// VALUES (@StudentId, @Notes, @CreatedByUserId, NOW(), FALSE);
|
||||
// SELECT LAST_INSERT_ID();",
|
||||
// new { StudentId = studentId, request.Notes, CreatedByUserId = createdByUserId });
|
||||
|
||||
return Task.FromResult(entry.Id);
|
||||
}
|
||||
|
||||
public Task Update(int entryId, UpdateEntryRequest request, int updatedByUserId)
|
||||
{
|
||||
var entry = _entries.FirstOrDefault(e => e.Id == entryId && !e.IsDeleted);
|
||||
if (entry is not null)
|
||||
{
|
||||
entry.Notes = request.Notes;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// REAL DATABASE VERSION (commented out):
|
||||
// ==========================================
|
||||
// await _db.ExecuteAsync(
|
||||
// @"UPDATE progress_entries
|
||||
// SET notes = @Notes, updated_by_user_id = @UpdatedByUserId, updated_at = NOW()
|
||||
// WHERE id = @EntryId AND is_deleted = FALSE",
|
||||
// new { EntryId = entryId, request.Notes, UpdatedByUserId = updatedByUserId });
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Soft-delete: marks the entry as deleted instead of removing it.
|
||||
/// The row stays in the database for auditing purposes.
|
||||
/// </summary>
|
||||
public Task SoftDelete(int entryId, int deletedByUserId)
|
||||
{
|
||||
var entry = _entries.FirstOrDefault(e => e.Id == entryId);
|
||||
if (entry is not null)
|
||||
{
|
||||
entry.IsDeleted = true;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// REAL DATABASE VERSION (commented out):
|
||||
// ==========================================
|
||||
// await _db.ExecuteAsync(
|
||||
// @"UPDATE progress_entries
|
||||
// SET is_deleted = TRUE,
|
||||
// deleted_by_user_id = @DeletedByUserId,
|
||||
// deleted_at = NOW()
|
||||
// WHERE id = @EntryId",
|
||||
// new { EntryId = entryId, DeletedByUserId = deletedByUserId });
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// =============================================================================
|
||||
// DummyStudentRepository.cs
|
||||
// =============================================================================
|
||||
// Hardcoded student data. The most interesting method here is
|
||||
// GetAccessibleStudents, which demonstrates the "scoped list" pattern.
|
||||
//
|
||||
// THE IDEA:
|
||||
// For single-resource endpoints (GET /students/101), the controller calls
|
||||
// AuthorizeAsync to check access. But for LIST endpoints (GET /students),
|
||||
// you can't load ALL students and then authorize each one — that's slow
|
||||
// and leaks the existence of records the user shouldn't see.
|
||||
//
|
||||
// Instead, the list query itself is scoped through the assignments table.
|
||||
// The query joins students through student_assignments, so it only returns
|
||||
// students the user is assigned to. Unauthorized records never leave the
|
||||
// database.
|
||||
// =============================================================================
|
||||
|
||||
using RolesAssignments.Models;
|
||||
|
||||
namespace RolesAssignments.Data;
|
||||
|
||||
public class DummyStudentRepository : IStudentRepository
|
||||
{
|
||||
// Simulates the students table
|
||||
private static readonly List<Student> _students = new()
|
||||
{
|
||||
new Student { Id = 101, Identifier = "Student A", ProgramYear = "2025-2026", Age = 8, IsDeleted = false },
|
||||
new Student { Id = 102, Identifier = "Student B", ProgramYear = "2025-2026", Age = 9, IsDeleted = false },
|
||||
new Student { Id = 103, Identifier = "Student C", ProgramYear = "2025-2026", Age = 7, IsDeleted = false },
|
||||
new Student { Id = 104, Identifier = "Student D", ProgramYear = "2025-2026", Age = 10, IsDeleted = false },
|
||||
};
|
||||
|
||||
// We need the assignment data to scope the list query
|
||||
private readonly IAssignmentRepository _assignments;
|
||||
|
||||
public DummyStudentRepository(IAssignmentRepository assignments)
|
||||
{
|
||||
_assignments = assignments;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns only the students that the given user is assigned to.
|
||||
/// In a real database, this would be a single SQL query with a JOIN
|
||||
/// through student_assignments. Here we simulate it in-memory.
|
||||
/// </summary>
|
||||
public Task<IEnumerable<StudentSummaryDto>> GetAccessibleStudents(int userId)
|
||||
{
|
||||
// In-memory simulation: we cast the dummy assignment repo to get
|
||||
// access to the underlying data. This is a prototype shortcut.
|
||||
// In production, this would be a SQL JOIN (see below).
|
||||
|
||||
// We use the DummyAssignmentRepository to simulate the JOIN.
|
||||
// For each student, check if the user has an active assignment.
|
||||
var now = DateTime.UtcNow;
|
||||
var assignmentRepo = (DummyAssignmentRepository)_assignments;
|
||||
|
||||
// Because we can't easily access the private static list from the
|
||||
// assignment repo in this simulation, we'll use GetActiveAssignment
|
||||
// for each student. This is an N+1 query in-memory — fine for a demo,
|
||||
// terrible in production (which is why the real version uses a JOIN).
|
||||
var results = new List<StudentSummaryDto>();
|
||||
foreach (var student in _students.Where(s => !s.IsDeleted))
|
||||
{
|
||||
var assignment = _assignments.GetActiveAssignment(userId, student.Id).Result;
|
||||
if (assignment is not null)
|
||||
{
|
||||
results.Add(new StudentSummaryDto
|
||||
{
|
||||
Id = student.Id,
|
||||
Identifier = student.Identifier,
|
||||
ProgramYear = student.ProgramYear,
|
||||
Age = student.Age,
|
||||
AssignmentType = assignment.AssignmentType.ToString()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// REAL DATABASE VERSION (commented out):
|
||||
// ==========================================
|
||||
// In production, this is ONE query — no N+1 problem:
|
||||
//
|
||||
// return await _db.QueryAsync<StudentSummaryDto>(
|
||||
// @"SELECT s.id AS Id,
|
||||
// s.identifier AS Identifier,
|
||||
// s.program_year AS ProgramYear,
|
||||
// s.age AS Age,
|
||||
// sa.assignment_type AS AssignmentType
|
||||
// FROM students s
|
||||
// INNER JOIN student_assignments sa ON sa.student_id = s.id
|
||||
// WHERE sa.user_id = @UserId
|
||||
// AND sa.is_active = TRUE
|
||||
// AND sa.start_date <= CURDATE()
|
||||
// AND (sa.end_date IS NULL OR sa.end_date >= CURDATE())
|
||||
// AND s.is_deleted = FALSE
|
||||
// ORDER BY s.identifier",
|
||||
// new { UserId = userId });
|
||||
//
|
||||
// Notice: there is NO role check in the query. The assignments table
|
||||
// already encodes who can see what — supervisors have Supervisor-type
|
||||
// assignments to all students, so they naturally appear in the JOIN.
|
||||
|
||||
return Task.FromResult<IEnumerable<StudentSummaryDto>>(results);
|
||||
}
|
||||
|
||||
public Task<Student?> GetById(int studentId)
|
||||
{
|
||||
var student = _students.FirstOrDefault(s => s.Id == studentId && !s.IsDeleted);
|
||||
return Task.FromResult(student);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// =============================================================================
|
||||
// IAssignmentRepository.cs
|
||||
// =============================================================================
|
||||
// This interface defines WHAT the assignment repository can do, without saying
|
||||
// HOW it does it. In this prototype, the "how" is hardcoded dummy data.
|
||||
// In production, the "how" would be a MySQL query via Dapper.
|
||||
//
|
||||
// WHY USE AN INTERFACE?
|
||||
// ---------------------
|
||||
// By coding against IAssignmentRepository (the interface) instead of a
|
||||
// concrete class, you can swap implementations without changing any other code.
|
||||
// Today: DummyAssignmentRepository (hardcoded). Tomorrow: DapperAssignmentRepository
|
||||
// (real database). The controllers, handlers, and everything else don't change —
|
||||
// they only know about the interface, not the implementation behind it.
|
||||
//
|
||||
// The swap happens in one place: Program.cs, where you register the service.
|
||||
// =============================================================================
|
||||
|
||||
using RolesAssignments.Models;
|
||||
|
||||
namespace RolesAssignments.Data;
|
||||
|
||||
public interface IAssignmentRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Looks up the active assignment between a specific user and a specific student.
|
||||
/// Returns null if no active assignment exists (meaning the user has no access
|
||||
/// to that student).
|
||||
///
|
||||
/// "Active" means:
|
||||
/// - is_active is true
|
||||
/// - start_date is today or earlier
|
||||
/// - end_date is null (ongoing) or today or later
|
||||
/// </summary>
|
||||
Task<StudentAssignment?> GetActiveAssignment(int userId, int studentId);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// =============================================================================
|
||||
// IStudentRepository.cs / IGoalRepository.cs / IProgressEntryRepository.cs
|
||||
// =============================================================================
|
||||
// All three repository interfaces in one file for this prototype.
|
||||
// Each defines the operations available for that entity.
|
||||
// =============================================================================
|
||||
|
||||
using RolesAssignments.Models;
|
||||
|
||||
namespace RolesAssignments.Data;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// STUDENT REPOSITORY
|
||||
// ---------------------------------------------------------------------------
|
||||
public interface IStudentRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a list of students the given user is allowed to see.
|
||||
/// This is the "scoped list" query — it filters at the database level
|
||||
/// so unauthorized student records never leave the data layer.
|
||||
/// </summary>
|
||||
Task<IEnumerable<StudentSummaryDto>> GetAccessibleStudents(int userId);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a single student by ID, or null if not found.
|
||||
/// NOTE: This does NOT check authorization — the controller is responsible
|
||||
/// for calling AuthorizeAsync before returning the data.
|
||||
/// </summary>
|
||||
Task<Student?> GetById(int studentId);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GOAL REPOSITORY
|
||||
// ---------------------------------------------------------------------------
|
||||
public interface IGoalRepository
|
||||
{
|
||||
Task<IEnumerable<Goal>> GetByStudentId(int studentId);
|
||||
Task<Goal?> GetById(int goalId);
|
||||
Task<int> Create(int studentId, CreateGoalRequest request, int createdByUserId);
|
||||
Task Update(int goalId, UpdateGoalRequest request, int updatedByUserId);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PROGRESS ENTRY REPOSITORY
|
||||
// ---------------------------------------------------------------------------
|
||||
public interface IProgressEntryRepository
|
||||
{
|
||||
Task<IEnumerable<ProgressEntry>> GetByStudentId(int studentId);
|
||||
Task<ProgressEntry?> GetById(int entryId);
|
||||
Task<int> Create(int studentId, CreateEntryRequest request, int createdByUserId);
|
||||
Task Update(int entryId, UpdateEntryRequest request, int updatedByUserId);
|
||||
Task SoftDelete(int entryId, int deletedByUserId);
|
||||
}
|
||||
Reference in New Issue
Block a user