fix(logs): resolve the five real defects surfaced by app_log
TenantInsurance reads ran through the tenant filter, which pins every query to the *requesting* user's environment. A clinic owner managing a doctor's contracts therefore read an empty set, recomputed version 1, and hit `uniq_tenant_insurance_version` on insert. The reads now bypass the filter — authorization is already established by resolveTargetEntity(), and the uuid-based paths re-assert ownership after loading. UserActiveContext::upsert() raced with itself: the panel fires several /oauth/userinfo requests at once, all saw no row, all inserted, and the losers died on a duplicate PRIMARY (closing the EntityManager with them). Replaced with INSERT ... ON DUPLICATE KEY UPDATE. A service that carries a treatment protocol but no catalog category is bad catalog data, not a system failure; it was logged at error level on every confirm and buried the real errors. Now a warning carrying the service id. Kavenegar's HTTP 431 says only "malformed request". The provider's own message and the token slot names are now logged so the template can actually be fixed in the panel; token values stay out of the log. Redis DSNs gained timeout/retry_interval/tcp_keepalive so a brief connection loss reconnects quietly instead of logging a warning each time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,20 +3,39 @@
|
||||
namespace App\Insurance\Repository;
|
||||
|
||||
use App\Insurance\Entity\TenantInsurance;
|
||||
use App\Shared\Tenant\TenantFilterScope;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
class TenantInsuranceRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
public function __construct(ManagerRegistry $registry, private readonly TenantFilterScope $tenantScope)
|
||||
{
|
||||
parent::__construct($registry, TenantInsurance::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* قراردادِ بیمه همیشه به محیطِ *مقصد* تعلق دارد، نه محیطِ کاربرِ درخواستدهنده:
|
||||
* مالک کلینیک قرارداد پزشکِ زیرمجموعه را میسازد و میبیند. با TenantFilter روشن،
|
||||
* این کوئریها به محیط خود کاربر محدود میشدند و ردیفِ موجود را نمیدیدند — نتیجهاش
|
||||
* ساختِ دوبارهٔ version=1 و خطای `uniq_tenant_insurance_version` بود.
|
||||
*
|
||||
* مجوزِ دیدنِ آن محیط قبلاً در `InsuranceController::resolveTargetEntity()` بررسی
|
||||
* شده و متدهایی که با uuid کار میکنند بعد از خواندن، مالکیت را دوباره میسنجند.
|
||||
*
|
||||
* @template T
|
||||
* @param callable():T $query
|
||||
* @return T
|
||||
*/
|
||||
private function unscoped(callable $query): mixed
|
||||
{
|
||||
return $this->tenantScope->withoutFilter($query);
|
||||
}
|
||||
|
||||
/** @return TenantInsurance[] */
|
||||
public function findActiveByTenant(string $entityType, int $entityId): array
|
||||
{
|
||||
return $this->createQueryBuilder('t')
|
||||
return $this->unscoped(fn (): array => $this->createQueryBuilder('t')
|
||||
->where('t.entityType = :type')
|
||||
->andWhere('t.entityId = :id')
|
||||
->andWhere('t.isActive = true')
|
||||
@@ -24,7 +43,7 @@ class TenantInsuranceRepository extends ServiceEntityRepository
|
||||
->setParameter('id', $entityId)
|
||||
->orderBy('t.insuranceId', 'ASC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
->getResult());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,7 +55,7 @@ class TenantInsuranceRepository extends ServiceEntityRepository
|
||||
*/
|
||||
public function findLatestByTenant(string $entityType, int $entityId): array
|
||||
{
|
||||
$rows = $this->createQueryBuilder('t')
|
||||
$rows = $this->unscoped(fn (): array => $this->createQueryBuilder('t')
|
||||
->where('t.entityType = :type')
|
||||
->andWhere('t.entityId = :id')
|
||||
->setParameter('type', $entityType)
|
||||
@@ -44,7 +63,7 @@ class TenantInsuranceRepository extends ServiceEntityRepository
|
||||
->orderBy('t.insuranceId', 'ASC')
|
||||
->addOrderBy('t.version', 'DESC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
->getResult());
|
||||
|
||||
$latest = [];
|
||||
foreach ($rows as $row) {
|
||||
@@ -56,12 +75,12 @@ class TenantInsuranceRepository extends ServiceEntityRepository
|
||||
|
||||
public function findByUuid(string $uuid): ?TenantInsurance
|
||||
{
|
||||
return $this->findOneBy(['uuid' => $uuid]);
|
||||
return $this->unscoped(fn (): ?TenantInsurance => $this->findOneBy(['uuid' => $uuid]));
|
||||
}
|
||||
|
||||
public function findActiveContract(string $entityType, int $entityId, int $insuranceId): ?TenantInsurance
|
||||
{
|
||||
return $this->createQueryBuilder('t')
|
||||
return $this->unscoped(fn (): ?TenantInsurance => $this->createQueryBuilder('t')
|
||||
->where('t.entityType = :type')
|
||||
->andWhere('t.entityId = :id')
|
||||
->andWhere('t.insuranceId = :ins')
|
||||
@@ -72,12 +91,12 @@ class TenantInsuranceRepository extends ServiceEntityRepository
|
||||
->orderBy('t.version', 'DESC')
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
->getOneOrNullResult());
|
||||
}
|
||||
|
||||
public function latestVersion(string $entityType, int $entityId, int $insuranceId): int
|
||||
{
|
||||
$max = $this->createQueryBuilder('t')
|
||||
$max = $this->unscoped(fn (): mixed => $this->createQueryBuilder('t')
|
||||
->select('MAX(t.version)')
|
||||
->where('t.entityType = :type')
|
||||
->andWhere('t.entityId = :id')
|
||||
@@ -86,7 +105,7 @@ class TenantInsuranceRepository extends ServiceEntityRepository
|
||||
->setParameter('id', $entityId)
|
||||
->setParameter('ins', $insuranceId)
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
->getSingleScalarResult());
|
||||
|
||||
return (int) ($max ?? 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user