Files
root 0f8ad86b4f
Deploy to Shared Hosting / Shared hosting deploy (push) Failing after 47s
Tests / PHPUnit (push) Successful in 1m20s
Add canonical user access profile table
Create user_access_profiles as a denormalized access index over the existing roles and user_roles tables. Store primary_category plus is_admin, is_teacher, and is_parent flags so permission checks can use a single canonical source for broad user groups.

Classify teacher and teacher_assistant/TA roles as teacher access, parent as parent access, and every other active staff role as admin access. Backfill all existing users during migration and keep profiles synchronized when role assignments are inserted, updated, or deleted.

Expose the computed access profile in web sessions and auth API responses while preserving the existing detailed roles array and route-filter behavior. Add unit coverage for TA, staff-admin, and multi-role parent/teacher classification.
2026-08-29 23:20:40 -04:00

131 lines
3.5 KiB
PHP

<?php
namespace App\Models;
use CodeIgniter\Model;
class UserRoleModel extends Model
{
protected $table = 'user_roles';
protected $primaryKey = 'id';
protected $returnType = 'array';
protected $allowedFields = [
'user_id',
'role_id',
'created_at',
'updated_at',
'updated_by',
'deleted_at'
];
protected $useTimestamps = true;
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
protected $deletedField = 'deleted_at';
protected $afterInsert = ['syncStaffDirectoryAfterWrite'];
protected $afterUpdate = ['syncStaffDirectoryAfterWrite'];
protected $afterDelete = ['syncStaffDirectoryAfterDelete'];
protected function syncStaffDirectoryAfterWrite(array $data): array
{
try {
$userId = (int) ($data['data']['user_id'] ?? 0);
if ($userId > 0) {
service('staffDirectorySync')->syncUser($userId);
model(UserAccessProfileModel::class)->syncUser($userId);
}
} catch (\Throwable $e) {
log_message('error', 'UserRoleModel role-derived sync failed: ' . $e->getMessage());
}
return $data;
}
protected function syncStaffDirectoryAfterDelete(array $data): array
{
try {
service('staffDirectorySync')->syncAll();
model(UserAccessProfileModel::class)->syncAll();
} catch (\Throwable $e) {
log_message('error', 'UserRoleModel role-derived delete sync failed: ' . $e->getMessage());
}
return $data;
}
/**
* ✅ Fetch all role names assigned to a specific user
*
* @param int $userId
* @return array Array of roles, e.g., [['name' => 'parent'], ['name' => 'teacher']]
*/
public function create()
{
$users = $this->db->table('users')
->select('users.*')
->join('user_roles', 'user_roles.user_id = users.id')
->join('roles', 'roles.id = user_roles.role_id')
->whereNotIn('roles.name', ['parent', 'teacher'])
->groupBy('users.id') // prevent duplicates if user has multiple roles
->get()
->getResultArray();
return view('expenses/create', ['users' => $users]);
}
/**
* 🔁 Deprecated: Use getAllRolesByUserId instead
* Get the first role name by user ID
*
* @param int $userId
* @return string|null
*/
public function getRolesByUserId(int $userId): array
{
$roles = $this->select('roles.name as role_name')
->join('roles', 'roles.id = user_roles.role_id')
->where('user_roles.user_id', $userId)
->get()
->getResultArray();
// Always return an array
return is_array($roles) ? $roles : [];
}
/**
* ✅ Insert or update a role assignment
*
* @param int $userId
* @param int $roleId
* @return bool|int
*/
public function updateOrInsertRole($userId, $roleId)
{
// Check if this exact (user_id, role_id) already exists
$exists = $this->where([
'user_id' => $userId,
'role_id' => $roleId
])->first();
if ($exists) {
// Already exists, nothing to do
return true;
}
// Otherwise, insert new user-role mapping
return $this->insert([
'user_id' => $userId,
'role_id' => $roleId,
'updated_by' => session()->get('user_id')
]);
}
}