forked from fa/breadcrumb-the-shire
Rename the top-level lib/ directory to core/ so the project root immediately communicates which code is core platform and which lives in modules/. PHP namespaces (MintyPHP\*) are unchanged — only the Composer PSR-4 path mapping moves from lib/ to core/. Module-internal lib/ directories (modules/*/lib/) are untouched. Updated across the full stack: - composer.json PSR-4 mapping - bootstrap entry points (web/index.php, bin/*, tests/bootstrap.php) - tooling configs (phpstan.neon, phpunit.xml, php-cs-fixer) - 26 architecture contract tests - enforcement-policy, guard-catalog, quality-gates - all documentation (CLAUDE.md, README, 25 docs/, .agents/skills/) - bin/qa-extended.sh search paths - .claude/settings.local.json permission paths Workflow: .agents/runs/CORE-LIB-RENAME-001/ (Analyst → Planner → Executor → Code Review (4 findings fixed) → Security Review → Acceptance Test → Finalizer) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
156 lines
6.0 KiB
PHP
156 lines
6.0 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Repository\User;
|
|
|
|
use MintyPHP\DB;
|
|
|
|
/** Retrieves user records and authorization snapshots; no write operations. */
|
|
class UserReadRepository implements UserReadRepositoryInterface
|
|
{
|
|
public function findAuthzSnapshot(int $userId): ?array
|
|
{
|
|
if ($userId <= 0) {
|
|
return null;
|
|
}
|
|
|
|
$row = DB::selectOne(
|
|
'select id, active, authz_version, locale, theme, current_tenant_id from users where id = ? limit 1',
|
|
(string) $userId
|
|
);
|
|
|
|
return $this->unwrap($row);
|
|
}
|
|
|
|
public function find(int $id): ?array
|
|
{
|
|
$row = DB::selectOne(
|
|
'select id, uuid, first_name, last_name, display_name, email, profile_description, job_title, phone, mobile, short_dial, address, postal_code, city, country, region, hire_date, email_verified_at, last_login_at, last_login_provider, password, locale, totp_secret, theme, primary_tenant_id, current_tenant_id, created_by, modified_by, created, modified, active, authz_version from users where id = ? limit 1',
|
|
(string) $id
|
|
);
|
|
return $this->unwrap($row);
|
|
}
|
|
|
|
public function findByUuid(string $uuid): ?array
|
|
{
|
|
$row = DB::selectOne(
|
|
'select id, uuid, first_name, last_name, display_name, email, profile_description, job_title, phone, mobile, short_dial, address, postal_code, city, country, region, hire_date, email_verified_at, last_login_at, last_login_provider, password, locale, totp_secret, theme, primary_tenant_id, current_tenant_id, created_by, modified_by, created, modified, active, authz_version, active_changed_at, active_changed_by from users where uuid = ? limit 1',
|
|
$uuid
|
|
);
|
|
return $this->unwrap($row);
|
|
}
|
|
|
|
public function findByEmail(string $email): ?array
|
|
{
|
|
$row = DB::selectOne(
|
|
'select id, uuid, first_name, last_name, display_name, email, profile_description, job_title, phone, mobile, short_dial, address, postal_code, city, country, region, hire_date, email_verified_at, last_login_at, last_login_provider, password, locale, totp_secret, theme, primary_tenant_id, current_tenant_id, created_by, modified_by, created, modified, active, authz_version, active_changed_at, active_changed_by from users where email = ? limit 1',
|
|
$email
|
|
);
|
|
return $this->unwrap($row);
|
|
}
|
|
|
|
public function listPrivilegedUserIdsByPermissionKeys(array $permissionKeys): array
|
|
{
|
|
$keys = array_values(array_unique(array_filter(array_map(
|
|
static fn ($key) => trim((string) $key),
|
|
$permissionKeys
|
|
))));
|
|
if (!$keys) {
|
|
return [];
|
|
}
|
|
|
|
$placeholders = implode(',', array_fill(0, count($keys), '?'));
|
|
$rows = DB::select(
|
|
'select distinct ur.user_id from user_roles ur ' .
|
|
'join roles r on r.id = ur.role_id and r.active = 1 ' .
|
|
'join role_permissions rp on rp.role_id = ur.role_id ' .
|
|
'join permissions p on p.id = rp.permission_id and p.active = 1 ' .
|
|
"where p.`key` in ($placeholders)",
|
|
...$keys
|
|
);
|
|
if (!is_array($rows)) {
|
|
return [];
|
|
}
|
|
|
|
$ids = [];
|
|
foreach ($rows as $row) {
|
|
$data = $row['ur'] ?? $row['user_roles'] ?? $row;
|
|
$userId = (int) ($data['user_id'] ?? $row['user_id'] ?? 0);
|
|
if ($userId > 0) {
|
|
$ids[] = $userId;
|
|
}
|
|
}
|
|
return array_values(array_unique($ids));
|
|
}
|
|
|
|
public function listIdsForAutoDeactivate(int $days, array $excludedUserIds, int $limit = 500): array
|
|
{
|
|
if ($days <= 0 || $limit <= 0) {
|
|
return [];
|
|
}
|
|
|
|
$excluded = array_values(array_unique(array_filter(array_map('intval', $excludedUserIds), static fn ($id) => $id > 0)));
|
|
$query = 'select id from users where active = 1 and coalesce(last_login_at, created) <= DATE_SUB(UTC_TIMESTAMP(), INTERVAL ' .
|
|
(int) $days .
|
|
' DAY)';
|
|
$params = [];
|
|
if ($excluded) {
|
|
$placeholders = implode(',', array_fill(0, count($excluded), '?'));
|
|
$query .= " and id not in ($placeholders)";
|
|
$params = array_map('strval', $excluded);
|
|
}
|
|
$query .= ' order by coalesce(last_login_at, created) asc limit ?';
|
|
$params[] = (string) $limit;
|
|
$rows = DB::select($query, ...$params);
|
|
if (!is_array($rows)) {
|
|
return [];
|
|
}
|
|
return $this->extractIdList($rows);
|
|
}
|
|
|
|
public function listIdsForAutoDelete(int $days, array $excludedUserIds, int $limit = 500): array
|
|
{
|
|
if ($days <= 0 || $limit <= 0) {
|
|
return [];
|
|
}
|
|
|
|
$excluded = array_values(array_unique(array_filter(array_map('intval', $excludedUserIds), static fn ($id) => $id > 0)));
|
|
$query = 'select id from users where active = 0 and active_changed_at is not null and active_changed_at <= DATE_SUB(UTC_TIMESTAMP(), INTERVAL ' .
|
|
(int) $days .
|
|
' DAY)';
|
|
$params = [];
|
|
if ($excluded) {
|
|
$placeholders = implode(',', array_fill(0, count($excluded), '?'));
|
|
$query .= " and id not in ($placeholders)";
|
|
$params = array_map('strval', $excluded);
|
|
}
|
|
$query .= ' order by active_changed_at asc limit ?';
|
|
$params[] = (string) $limit;
|
|
$rows = DB::select($query, ...$params);
|
|
if (!is_array($rows)) {
|
|
return [];
|
|
}
|
|
return $this->extractIdList($rows);
|
|
}
|
|
|
|
private function unwrap(?array $row): ?array
|
|
{
|
|
if (!$row) {
|
|
return null;
|
|
}
|
|
return $row['users'] ?? null;
|
|
}
|
|
|
|
private function extractIdList(array $rows): array
|
|
{
|
|
$ids = [];
|
|
foreach ($rows as $row) {
|
|
$data = $row['users'] ?? $row;
|
|
$id = (int) ($data['id'] ?? $row['id'] ?? 0);
|
|
if ($id > 0) {
|
|
$ids[] = $id;
|
|
}
|
|
}
|
|
return array_values(array_unique($ids));
|
|
}
|
|
}
|