[CL-OBJECT-ACCESS-SLUG] objectAccess: accept slug + id (fix AI Анализ "Object not found") #201
Closed
andrei
wants to merge 1 commit from
fix/cl-object-access-slug into master
pull from: fix/cl-object-access-slug
merge into: europa-tech-srl:master
europa-tech-srl:master
europa-tech-srl:agent/devops/pix-15311
europa-tech-srl:agent/devops/pix-15306
europa-tech-srl:agent/cto/pix-15177
europa-tech-srl:main
europa-tech-srl:agent-fullstack/pix-8664
europa-tech-srl:feature/claude-cl1176-payout-e2e
europa-tech-srl:feature/claude-cl1176-guard-dbname-fix
europa-tech-srl:feature/claude-cl1176-e2e-in-ci
europa-tech-srl:feature/claude-cl1176-conditional-prisma-mock
europa-tech-srl:feature/claude-cl1176-exchange-buyback-e2e
europa-tech-srl:agent/devops/pix-14678
europa-tech-srl:feature/claude-cl1176-webhook-distribution-e2e
europa-tech-srl:feature/claude-cl1176-type-honesty
europa-tech-srl:feature/claude-cl1176-cancel-expire-e2e
europa-tech-srl:fix/sentry-lenis-empty-touch
europa-tech-srl:agent/fullstack/pix-13735-news-title-scope
europa-tech-srl:agent/fullstack/pix-13735-newsai-fixtures
europa-tech-srl:agent/fullstack/pix-13735-news-safelist
europa-tech-srl:fix/low-audit-20260719
europa-tech-srl:chore/monthly-deps-20260719
europa-tech-srl:agent/compliance/pix-13735
europa-tech-srl:agent/fullstack/pix-14259
europa-tech-srl:fix/blog-properties-links-20260718
europa-tech-srl:fix/news-sitemap-public-parity-20260718
europa-tech-srl:fix/nowpayments-probe-alert-hygiene-20260718
europa-tech-srl:agent/sentry/pix-14176
europa-tech-srl:agent/cto/pix-14168
europa-tech-srl:agent/fullstack/pix-14161
europa-tech-srl:agent/cto/pix-14162
europa-tech-srl:codex/react-doctor-suppression-20260717
europa-tech-srl:agent/fullstack/pix-14153
europa-tech-srl:agent/cto/pix-14151
europa-tech-srl:agent/fullstack/pix-14150
europa-tech-srl:agent/fullstack/pix-14132
europa-tech-srl:agent/fullstack/pix-14144
europa-tech-srl:deploy/pix-14136
europa-tech-srl:agent/fullstack/pix-14136
europa-tech-srl:codex/docs-audit-status-20260717
europa-tech-srl:codex/fix-ma0710-m2-lending-confirmations-20260717
europa-tech-srl:codex/fix-ma0710-m1-lazy-hydrate-accessibility-20260717
europa-tech-srl:codex/fix-ma0710-h5-timelock-bootstrap-contract-20260717
europa-tech-srl:codex/fix-ma0710-h4-eurt-timeout-reconciliation-20260717
europa-tech-srl:codex/fix-ma0710-h3-purchase-accounting-20260717
europa-tech-srl:codex/fix-ma0710-h2-stripe-post-actions-20260716
europa-tech-srl:agent/fullstack/pix-14122
europa-tech-srl:agent/fullstack/pix-14118
europa-tech-srl:agent/cto/pix-14065
europa-tech-srl:codex/fix-hero-fallback-copy-20260716
europa-tech-srl:agent/fullstack/pix-14030
europa-tech-srl:codex/fix-react-doctor-lazyhydrate-20260716
europa-tech-srl:agent/fullstack/pix-13809
europa-tech-srl:codex/stripe-payment-create-atomic-20260714
europa-tech-srl:agent/devops/pix-13984
europa-tech-srl:agent/cto/pix-13978
europa-tech-srl:agent/compliance/pix-13968
europa-tech-srl:agent/fullstack/pix-13935
europa-tech-srl:agent/fullstack/pix-13886
europa-tech-srl:agent/fullstack/pix-13744
europa-tech-srl:fix/pix-13874-zod-record-schema
europa-tech-srl:agent/fullstack/pix-13760-v2
europa-tech-srl:fix/pix-13839-compliance-locale
europa-tech-srl:agent/fullstack/pix-13846
europa-tech-srl:agent/fullstack/pix-13760
europa-tech-srl:fix/pix-13844-cabinet-meta
europa-tech-srl:agent/devops/pix-13833-pr1074
europa-tech-srl:fix/PIX-13775-rooms-share-price
europa-tech-srl:fix/pix-13738-compliance-copy
europa-tech-srl:agent/cto/pix-13726
europa-tech-srl:codex/institutional-bulk-lock-order-20260711
europa-tech-srl:codex/payment-lock-legacy-audit-20260711
europa-tech-srl:codex/payment-lock-order-20260711
europa-tech-srl:codex/idempotency-legacy-compat-20260710
europa-tech-srl:codex/idempotency-fingerprint-conflict-20260710
europa-tech-srl:codex/stripe-fallback-idempotency-20260710
europa-tech-srl:codex/payout-serialization-retry-20260710
europa-tech-srl:codex/tg-balance-idempotency-20260710
europa-tech-srl:codex/lending-repay-race-20260710
europa-tech-srl:feature/claude-vault-withdraw-failclosed-20260710
europa-tech-srl:feature/claude-mega-audit-20260710-5
europa-tech-srl:feature/claude-mega-audit-20260710-4
europa-tech-srl:feature/claude-mega-audit-20260710-3
europa-tech-srl:feature/claude-mega-audit-20260710-2
europa-tech-srl:fix/qa-password-reset-email
europa-tech-srl:feature/claude-api-eaddrinuse-retry
No reviewers
No labels
ci-failing
duplicate
needs-rebase
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set.
Reference
europa-tech-srl/europatech!201
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/cl-object-access-slug"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Что сделано
Изменил
api/src/services/objectAccess.service.ts:findObjectByIdOrSlug(idOrSlug)—prisma.propertyObject.findFirst({ where: { OR: [{ id }, { slug }] }, select: { id, status } }). Один query вместо двух.assertObjectAccessible(idOrSlug, userId?)— теперь принимает slug ИЛИ id, использует resolved каноническийobject.idдляhasObjectOwnershipAccess(без этого rooms relationroom: { objectId }ищет по slug → false → ownership denied).assertObjectPublic(idOrSlug)— то же.{ status }(не вся prisma row) — preserve API shape, не ломает existing callers.(object) => object.idвgetPublicVisibleObjectIds— добавил explicit type{ id: string }.Также добавил
api/src/services/__tests__/objectAccess.service.test.ts— 8 vitest cases: slug→id resolved, canonical id directly, 404 on miss, ownership grants for non-public statuses, ownership deny throws, public path, non-public-status rejected, slug-not-found.Зачем
Live evidence prod (CEO репорт скриншот #2):
CEO открыл
/objects/hotel-baistrocchi-wellness-longevity-center/passport, в AI Анализ панели нажал «Обновить анализ» → 2× toast «Object not found». Анализ уже отображался (score 53/100), значит GET/api/analysis/<slug>работал (черезresolveObjectIdвpropertyAnalysis/analysis.ts). Но POST/api/analysis/<slug>/generate→generateAnalysisUsercontroller →assertObjectAccessible(slug, userId)→prisma.findUnique({where:{id:slug}})→ null → AppError 404.Тот же баг ломал:
income.controller(assetTabAccessчерезassertObjectAccessible)requireObjectAccessmiddleware (использует обе функции)dao.routes(assertObjectPublic)yieldDistribution.routes(dynamic importassertObjectAccessible)Все эти endpoints возвращали 404 если controller получал slug, что для public passport URL — норма.
Fix в одной точке (objectAccess.service) автоматически чинит весь импакт-кластер.
План тестирования
npx vitest run src/services/__tests__/objectAccess.service.test.ts— 8/8 PASSnpx vitest run src/services/__tests__/objectAccess.service.test.ts src/controllers/income.controller.test.ts src/services/analytics.service.test.ts— 38/38 PASS (regression-safe для existing callers через mock{status:'ACTIVE'}return shape)npx tsc --noEmit— clean (наш файл + bonus pre-existing TS7006 closed)npx eslint src/services/objectAccess.service.ts src/services/__tests__/objectAccess.service.test.ts --max-warnings 0— clean (exit=0)После merge + deploy через
bash scripts/deploy.sh:/api/analysis/hotel-baistrocchi-wellness-longevity-center/generateдолжен вернуть 201 (не 404).Где могу ошибаться
object(полная findUnique row) на{ status }. Все callers (8 мест по grep) деструктурируют как{ status }или игнорируют return — проверил vitest 38/38 проходит, в т.ч. income.controller.test ожидает{ status: 'ACTIVE' }. OK.hasObjectOwnershipAccess(userId, canonical_id)— теперь использует resolved id. Тестируется в новом vitest case (mock проверяетroom: { objectId: 'obj-002' }). Backward-compat: callers вне service используют только status, не объект.Root cause: assertObjectAccessible + assertObjectPublic used prisma.findUnique({where:{id}}) which rejects slug. analysis.controller generateAnalysisUser passes raw req.params.objectId (slug for public passport URLs) -> AppError 404 -> "Object not found" toast on CEO browse of /objects/<slug>/passport "Обновить анализ" button. Same pattern was breaking: - analysis.controller (generateAnalysisUser) - income.controller (assetTabAccess) - requireObjectAccess middleware - dao.routes - yieldDistribution.routes Fix: - Replace findUnique({where:{id}}) with findFirst({where:{OR:[{id},{slug}]}}) in single shared helper findObjectByIdOrSlug. - assertObjectAccessible + assertObjectPublic now resolve slug to canonical id before ownership lookup (hasObjectOwnershipAccess uses canonical id, not raw slug, so room: {objectId} relation matches). - Return narrowed {status} only (not the prisma row) to preserve original API. - Boy Scout: fixed pre-existing TS7006 in getPublicVisibleObjectIds map callback. Tests: 8 new vitest cases cover slug-resolved-to-id, canonical-id, 404 on miss, ownership-grants-for-non-public, ownership-deny-throws, public path, non-public-status-rejected, slug-not-found. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Pull request closed