Commit Graph

661 Commits

Author SHA1 Message Date
0b1f03333a Merge pull request 'feat(telemetry): enrich VIN decode events for Süper Panel observability' (#32) from dev into main 2026-05-18 07:56:02 +00:00
4645c2700e feat(telemetry): enrich VIN decode events for Süper Panel observability
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Süper Panel VIN Decode Observability Module (SP-VIN-001) prerequisite.
The dashboard needs provider chain + cache + fallback context the
frontend can't see — emit those events from the backend.

VehiclesService.decodeVin now emits, at every terminal path:

1. provider_response_received (one per provider that ran)
   - provider, response_time_ms, status (success/no_data), cached,
     vin_brand, vin_sanitized, attempt_in_chain
   - Derived from ctx.timings.{pcat,emex,pl24,vin_api}; only providers
     that actually executed get an event.

2. provider_fallback_triggered (between consecutive attempts)
   - from_provider, to_provider, reason (timeout if budget aborted,
     else no_data), auto, attempt_number.

3. vin_decode_succeeded — winning provider, cache_hit, cache_source,
   response_time_ms, partial_result, fallback_used, provider_attempts,
   pl24_circuit_open, vin_sanitized.

4. vin_decode_failed — error_code (BUDGET_EXCEEDED | UNKNOWN_VIN),
   error_message, provider_attempted, response_time_ms, vin_sanitized.

Wired at three terminal points:
- DB cache hit (existing vehicle, no chain run)
- Unknown VIN failure (chain returned null)
- Full chain success (savedVehicle return)

Notes:
- VINs are sanitized (`WAUZZZ8K****`) before leaving the backend.
- Emission is wrapped in try/catch; a PostHog hiccup never breaks a
  user-facing decode.
- Frontend's legacy `vin_decoded` / `vin_decode_success` /
  `vin_decode_error` events stay as-is. The new backend events live
  alongside them with richer props.
- ML decoder fields default to false — VAG ML pipeline lands later.

Spec updated to pass the new PostHogService mock.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 10:55:47 +03:00
2fe8828555 Merge pull request 'feat(internal-admin): brand reassignment' (#31) from dev into main 2026-05-18 07:35:45 +00:00
b7a5b6996b feat(internal-admin): brand reassignment — Phase F
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
POST /internal/admin/subscriptions/:id/brands  { brandIds[], reason, founderId }

- Only operates on active or trial subscriptions.
- Refuses Full plan (brandCount=0) — that tier auto-grants all brands.
- brandIds.length must exactly match plan.brandCount, no duplicates.
- Each brand ID must exist and be active.
- Replaces the user_brands rows for the subscription atomically (delete
  + insert; same-row contention is microseconds, panel calls are serial
  per founder).
- Logs the old → new brand sets for auditability.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 10:35:43 +03:00
d22662acb3 perf(pcat): demote e-acca.com to the end of the JWT site round-robin
PCAT auth pool warm-up always started with e-acca.com, which doesn't
load through the new DataImpulse rotating proxy — page.goto sits on
the 30s navigation timeout and only then falls through to the next
candidate. The remaining sites (alkatalog, auto-komplekt, autotrade,
…) reach the upstream widget in 3-10s through the same proxy, so an
on-demand acquireSession() that lands during a cold start spends 30s
blocked on the dead first hop before any retry can succeed.

Move e-acca to the end of the list. New cold-pool capture finishes
in ~4s on the first reachable site (alkatalog) instead of 30s+9s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 10:32:32 +03:00
8b47aba3c3 Merge pull request 'feat(internal-admin): refund + generalize subscription extend' (#30) from dev into main 2026-05-18 07:03:15 +00:00
85d0d791ed feat(internal-admin): refund + generalize subscription extend — Phase E
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Refund (Stripe API)
- StripeService.refundPayment({ paymentId, amount?, reason, founderId })
  is a new public method that wraps stripe.refunds.create:
  * Requires the payment to have a stripePaymentIntentId (post-Iyzico).
  * Refuses payments not in completed/partially_refunded status.
  * Partial refund: amount must be in 1..payment.amount (kuruş).
  * Sends panel_* metadata to Stripe for the founder/reason audit trail.
  * Flips payments.status to refunded / partially_refunded.
  * Appends a dated reason line to payments.admin_note.
  * Captures a `payment_refunded` PostHog event (via:'super_panel').
  * Does NOT cancel the subscription — that's a separate decision.
- New endpoint POST /internal/admin/payments/:id/refund behind the
  InternalTokenGuard, body { amount?, reason, founderId }.
- Wired through PaymentsAdminController in InternalAdminModule;
  StripeModule imported.

Extend (goodwill / bonus time)
- BillingService.extendTrial now accepts both trial AND active
  subscriptions (was trial-only). Same end-date semantics
  (base = max(now, current endDate)). Response now also returns
  subscriptionStatus so the panel can surface the right copy.
- Endpoint URL kept as /trial/extend for backward compatibility; the
  panel decides the user-facing label ("Trial uzat" vs "Bonus süre
  ekle / Goodwill") based on current status.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 10:03:03 +03:00
d2fb64484c perf(emex): plain-HTTP fast path with Playwright fallback (Tier 3-A)
Probe (scripts/dev) confirmed that everything PartsPanel reads from an
emexdwc.ae leaf — parts table, hotspot coordinates, and the schema image
URL — is fully server-rendered in Unit.aspx HTML. Parts arrive as
`<tr name>` rows with `td[name=c_oem|c_pnc|c_name]`; hotspots are
`<div class="dragger g_highlight" name=N style="margin-top:Ypx; margin-left:Xpx; ...">`
with the coords already in image-natural pixel space; the image URL is
in `<img class="dragger" src=...laximo...>` and its native dims can be
read from the first 24 bytes of the GIF/PNG via a Range GET.

Behaviour:
1. fetchCategoryParts now tries fetchCategoryPartsViaHttp first — two
   sequential GETs (QuickDetails → Unit) + a Range GET for image dims.
2. If the HTML yields ≥1 part, we return it.
3. If the HTML returns no Unit.aspx anchor, or 0 parts, we fall back to
   the existing Playwright scraper (same code path as before).

The plain-HTTP path skips the ~1-2s browser launch, sidesteps the
3-page semaphore in EmexBrowserService (concurrency cap was throttling
prefetch fan-out), and uses no chromium memory. Measured on dev with 5
fresh-ssd Renault Espace IV leaves: 4.9-5.5s wall per leaf (vs 6-7s on
the Tier 1 browser path, vs 12-14s pre-Tier-1). The 6th sample
(stale-ssd Fren Kaliyeri) failed both paths identically — confirms the
plain-HTTP path doesn't introduce new failure modes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 09:56:53 +03:00
e458cbc524 Merge pull request 'feat(internal-admin): plan change + cancel/resume' (#29) from dev into main 2026-05-18 06:43:29 +00:00
37a5e8f0f7 feat(internal-admin): plan change + cancel/resume — Phase D
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Three new endpoints on /internal/admin/subscriptions/:id complete the
Süper Panel billing surface.

POST .../change-plan  { newPlanId, reason, founderId }
- Only for active or trial subscriptions.
- Refuses no-op (already on that plan) and inactive plans.
- Updates planId; leaves userBrands intact so the founder can decide.
- Response includes brandReassignmentNeeded flag when the new plan's
  brandCount diverges from the current user-brand count (the panel
  surfaces a warning so the founder reaches out).

POST .../cancel       { reason, founderId }
- Active or trial → cancelled (sets cancelledAt = now).
- Refuses already-cancelled or expired.

POST .../resume       { reason, founderId }
- Cancelled → active (clears cancelledAt).
- All other states rejected.

Controller cleanup: factored requireFounder + requireReason guards so
every endpoint enforces the same validation contract uniformly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 09:43:19 +03:00
a25a837401 Merge pull request 'feat(internal-admin): trial extend + manual activate' (#28) from dev into main 2026-05-18 06:39:28 +00:00
0e83353f4e feat(internal-admin): trial extend + manual activate — Phase C
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Two new endpoints on /internal/admin/subscriptions/:id behind the
InternalTokenGuard.

POST .../trial/extend     { days, reason, founderId }
- Only operates on status='trial' subscriptions.
- 1..90 day clamp; new endDate = max(now, current endDate) + days
  (never shrinks the trial window).
- Returns previous/new endDate + daysAdded.

POST .../activate         { reason, founderId }
- Wraps SubscriptionsService.activateSubscription which handles
  status transition, startDate/endDate by billing period, and
  brand auto-assignment for Full plan.
- Refuses already-active, cancelled, or expired subscriptions.

Wiring
- BillingService + BillingController added to InternalAdminModule.
- SubscriptionsModule imported so we can call activateSubscription.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 09:39:18 +03:00
46ac5d6928 Merge pull request 'feat(internal-admin): user lifecycle (suspend/reactivate/ban)' (#27) from dev into main 2026-05-18 06:35:06 +00:00
96a9d11015 feat(internal-admin): user lifecycle — suspend / reactivate / ban
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Süper Panel Phase 7 — Phase B. Founder can suspend, reactivate, or ban a
Sase user from the panel. Status enforced in the AuthGuard so blocked
users can no longer make authenticated requests.

Schema (migration 0006)
- users.status varchar(20) default 'active' — active|suspended|banned
- users.status_reason text — free-text reason set on transition
- users.status_changed_at, status_changed_by uuid — audit metadata
- users_status_idx

Auth
- AuthGuard rejects 'suspended' / 'banned' with TR-localized message.
- auth.ts: declared `status` as a Better Auth additionalField so the
  session.user object exposes it (matches how `role` is wired).

Endpoints (InternalTokenGuard)
- POST /internal/admin/users/:id/suspend     { reason, founderId }
- POST /internal/admin/users/:id/reactivate  { founderId }
- POST /internal/admin/users/:id/ban         { reason, founderId }

Service
- LifecycleService.setStatus():
  - refuses to touch admin-role users
  - refuses no-op transitions (already in target state)
  - refuses suspended→banned→suspended downgrade path (must reactivate first)
  - on suspend/ban: deletes all sessions for the user (immediate sign-out)
  - returns { from, to, sessionsKilled, changedAt }

Wiring
- LifecycleService + LifecycleController added to InternalAdminModule.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 09:34:52 +03:00
3514272936 perf(emex): replace networkidle + 2s sleeps with targeted selector waits
EMEX leaf parts fetch was averaging 12.5s cold per category (Honda Civic
prod traces). Almost all of that lived in the scraper's two waterfall page
loads, each of which (a) waited for `networkidle` — which only fires after
analytics/ads stop chattering — then (b) slept an unconditional 2 seconds.
The actual extraction work runs in <100ms once the DOM is parsed.

Three focused changes in scripts/emex-vin-scraper.js:

1) getParts() resolves the Unit.aspx URL via a plain `fetch()` of
   QuickDetails.aspx instead of opening it in the browser. Probe showed
   the page is fully server-rendered and the anchor is present in the
   initial HTML, so the browser stage is dead weight (~3s saved). Falls
   back to a browser load if the fetch fails or the anchor isn't there,
   so catalogs that gate the link behind JS still work.

2) Both remaining `page.goto()` calls in getParts/getCategories/
   getCategoryTree drop `waitUntil:'networkidle'` for `'domcontentloaded'`
   plus a targeted `waitForSelector('img.dragger' | 'a[href*=…]', {timeout:5000-8000})`.
   `.catch(()=>null)` makes the wait advisory — the evaluate() below has
   its own null-safe fallbacks — but in practice the selector is present
   well before networkidle would have fired.

3) Removes the two unconditional `setTimeout(r,2000)` sleeps in getParts.
   They predate the selector-wait pattern and were belt-and-braces.

Expected: ~12.5s → ~3-5s cold leaf fetch. Warm path (DB-cached) is
unchanged at ~45ms. Dev-only push for verification before main merge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 09:24:38 +03:00
223c3f7045 Merge pull request #26 — fix(docker): unignore scripts/ 2026-05-18 06:08:25 +00:00
7c430635d8 fix(docker): unignore scripts/ so EMEX VIN scraper ships to production
`.dockerignore` was excluding the entire `scripts/` tree, which meant
the build stage never received `scripts/emex-vin-scraper.js`. The
production-stage `COPY --from=build /app/scripts ./scripts` added in
the previous commit therefore failed with "/app/scripts: not found"
and the deploy aborted. Unignore the directory; runtime-only files
can be excluded individually if any get added later.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 09:08:13 +03:00
ee8760eb33 Merge pull request #25 — fix(emex): scripts/ + chromium executablePath 2026-05-18 05:45:39 +00:00
a1e80c042f fix(emex): ship runtime scripts/ in prod image and forward chromium executable path
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
EMEX-decoded vehicles surfaced empty category pages on prod because two
runtime prerequisites were absent from the production image:

1. `scripts/emex-vin-scraper.js` was never copied — the build stage's
   `COPY . .` brings it in but the production stage only cherry-picks
   `apps/api/dist`, `drizzle`, and `start.sh`. Every EMEX leaf hit
   therefore failed with "Scraper file not found at: /app/scripts/…"
   and the category page rendered "Bu kategori icin parca bulunamadi."
2. `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser` was
   exported in the Dockerfile but never reached `chromium.launch()` —
   that env var is consumed by `playwright install`, not at runtime.
   Playwright fell back to its bundled headless-shell cache path
   (`/root/.cache/ms-playwright/chromium_headless_shell-*/…`) which
   does not exist on the alpine image, so even with the scraper file
   present the browser pool init would have kept failing.

Fix:
- Dockerfile: `COPY --from=build /app/scripts ./scripts`.
- `emex.browser.ts` + `parts-catalogs-auth.service.ts`: read
  `process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` and pass it as
  `executablePath` to `chromium.launch()` when set.

Verified on prod container: `ls /app/scripts` → missing pre-fix; the
binary at `/usr/bin/chromium-browser` exists, so the env-var hand-off
will resolve cleanly once the new image lands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 08:45:18 +03:00
17bd256979 Merge pull request 'chore(deploy): propagate INTERNAL_API_TOKEN to api service' (#24) from dev into main 2026-05-17 21:57:41 +00:00
7e56ebbcce chore(deploy): propagate INTERNAL_API_TOKEN to api service
Süper Panel calls /internal/admin/* endpoints with X-Internal-Token
header. Coolify env value needs to land inside the api container —
docker compose only interpolates listed env vars.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 00:57:31 +03:00
eebab6c7de Merge pull request #23 — readonly impersonation 2026-05-17 21:48:58 +00:00
2583b781ec feat(internal-admin): readonly impersonation for Süper Panel
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Lets the founder open a target user's session in a new tab from the
panel for debugging. Read-only enforced server-side — any non-GET
request from an impersonated session returns 403.

Schema
- sessions.impersonated_by (uuid, nullable) — founder Better Auth user id
- sessions.impersonation_readonly (bool, default false)
- index on impersonated_by

Service
- ImpersonationService.createReadonlySession({ targetUserId, founderId,
  ttlMinutes, reason, ipAddress, userAgent }):
  - Random sessionId + token (32 bytes hex each)
  - TTL clamped 1..60 min, default 15
  - Refuses to impersonate admin users
  - Inserts sessions row; signs cookie value with HMAC-SHA256(BETTER_AUTH_SECRET)
    matching better-call's signCookieValue format
  - Returns { cookieName, cookieValue, expiresAt, sessionId }

Guard
- ImpersonationReadonlyGuard runs after AuthGuard, before RolesGuard.
- GET/HEAD/OPTIONS pass through.
- For other methods: looks up sessions.impersonated_by + impersonation_readonly
  by request.session.id; throws ForbiddenException if both truthy.

Endpoints (InternalAdminModule)
- POST /internal/admin/users/:id/impersonate-readonly [InternalTokenGuard]
  body: { ttlMinutes, reason, founderId }
  returns: { redirectUrl, expiresAt, sessionIdPrefix }
  Hand-off is via signed consume URL (cross-origin Set-Cookie limitations).
- GET /admin/impersonate/consume?t=<signed> [@Public]
  Verifies HMAC-signed payload (<=60s validity), sets the Better Auth session
  cookie on sase.tr, redirects to /. One-shot.

Wiring
- InternalAdminModule imported in AppModule.
- ImpersonationReadonlyGuard registered as APP_GUARD between Auth and Roles.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 00:48:39 +03:00
79a261612f Merge pull request #22 — InternalTokenGuard + FN-411..FN-420 roll-up 2026-05-17 21:31:37 +00:00
d1c78f7b89 feat(internal-admin): add InternalTokenGuard for Süper Panel admin calls
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled
Foundation guard for /internal/admin/* endpoints called from Süper Panel
(sp.semih.ai) over Coolify internal network. Verifies X-Internal-Token
header against INTERNAL_API_TOKEN env with constant-time compare.

Not yet wired to any endpoint — internal-admin module/controllers will
land in follow-up commits as panel-side mutation features ship.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 00:31:05 +03:00
Fusion
4fc7d35c7a feat(FN-420): UX spec for VIN-decode error branches (subscription-wall, unknown-VIN)
Commits merged:
- feat(FN-420): UX spec for VIN-decode error branches (subscription-wall, unknown-VIN)

Files changed:
.../FN-420-vin-decode-error-branches-ux-spec.md    | 280 +++++++++++++++++++++
 1 file changed, 280 insertions(+)

Fusion-Task-Id: FN-420
2026-05-17 10:25:10 +00:00
Fusion
5118c8e5e9 feat(FN-415): add locator-stability + hit-target verification tests
Commits merged:
- feat(FN-415): add locator-stability + hit-target verification tests

Files changed:
apps/api/src/telemetry/__tests__/telemetry.spec.ts |  8 +++
 .../src/routes/__tests__/dashboard-search.test.tsx | 67 ++++++++++++++++++++++
 2 files changed, 75 insertions(+)

Fusion-Task-Id: FN-415

Fusion-Task-Lineage: 01e6f3d2-cd2e-4023-b538-ea54926b4f87
2026-05-17 06:17:51 +00:00
Fusion
9b1fc48a54 feat(FN-412): merge fusion/fn-412 2026-05-17 00:56:00 +00:00
Fusion
78ab45d512 feat(FN-411): Merge pull request 'dev' (#3) from dev into main (+16 more)
Commits merged:
- docs(FN-411): scoping output — spawned FN-415 (FN-367 mechanical verification) and FN-416 (FN-368 mechanical verification)
- Merge pull request #21 — feat(FN-403): parts panel manifest
- Merge pull request #20 — feat(FN-401): blocker note
- Merge pull request #19 — feat(FN-400): selector manifest
- Merge pull request #18 — feat(FN-399): PostHog cross-session funnel audit
- Merge pull request #17 — chore(payments): Stripe-only
- Merge pull request #16 — feat(FN-395): P1 candidate shortlist
- Merge pull request #15 — broaden leaf detection
- fix(categories): broaden leaf detection to all /extern/*/{vin,mdl}_items endpoints
- Merge pull request #14 — fix(pl24): Ford VIN flow
- Merge pull request 'dev' (#13) from dev into main
- Merge pull request 'feat(FN-367): add inline retry affordance after VIN decode failure (FN-367, gitea #11)' (#12) from dev into main
- Merge pull request 'dev' (#9) from dev into main
- Merge pull request 'dev' (#8) from dev into main
- Merge pull request 'docs(readme): spacing tweak — measure cache-enabled dev deploy speed' (#7) from dev into main
- Merge pull request 'docs(readme): add staging URL — gitea deploy flow smoke test' (#4) from dev into main
- Merge pull request 'dev' (#3) from dev into main

Files changed:
SCOPING-FN-411.md                             | 85 +++++++++++++++++++++++++++
 apps/api/src/catalog/catalog.service.ts       |  8 ++-
 apps/api/src/categories/categories.service.ts | 23 +++++---
 3 files changed, 107 insertions(+), 9 deletions(-)

Fusion-Task-Id: FN-411
2026-05-16 23:54:44 +00:00
Semih
d4baececee chore: remove playwright repro scratch file
Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
03beff1931 fix(vehicles): make decoded vehicles readable by any authenticated user
Decoded vehicle data is shared across the platform — once any user
decodes a VIN, the vehicle, its categories, and its parts should be
visible to every authenticated user. The previous getById enforced a
user↔vehicle junction via inner join, returning 404 "Araç bulunamadı"
when a user tried to view a vehicle they hadn't decoded themselves.
This surfaced as "Veriler yüklenirken bir hata oluştu" on the category
detail page for any vehicle the current user wasn't linked to.

Drop the ownership filter from getById; the userVehicles junction is
now used only for per-user history listing and delete operations.
Verified with Playwright: GET /api/vehicles/.../categories/... was
returning 404 from the ownership check for non-owner users.

Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
36fbc08508 chore: remove playwright repro scratch file
Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
89232179b0 fix(vehicles): make decoded vehicles readable by any authenticated user
Decoded vehicle data is shared across the platform — once any user
decodes a VIN, the vehicle, its categories, and its parts should be
visible to every authenticated user. The previous getById enforced a
user↔vehicle junction via inner join, returning 404 "Araç bulunamadı"
when a user tried to view a vehicle they hadn't decoded themselves.
This surfaced as "Veriler yüklenirken bir hata oluştu" on the category
detail page for any vehicle the current user wasn't linked to.

Drop the ownership filter from getById; the userVehicles junction is
now used only for per-user history listing and delete operations.
Verified with Playwright: GET /api/vehicles/.../categories/... was
returning 404 from the ownership check for non-owner users.

Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
b8c9fa17ae chore: remove playwright repro scratch file
Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
8d7fd42fb7 fix(vehicles): make decoded vehicles readable by any authenticated user
Decoded vehicle data is shared across the platform — once any user
decodes a VIN, the vehicle, its categories, and its parts should be
visible to every authenticated user. The previous getById enforced a
user↔vehicle junction via inner join, returning 404 "Araç bulunamadı"
when a user tried to view a vehicle they hadn't decoded themselves.
This surfaced as "Veriler yüklenirken bir hata oluştu" on the category
detail page for any vehicle the current user wasn't linked to.

Drop the ownership filter from getById; the userVehicles junction is
now used only for per-user history listing and delete operations.
Verified with Playwright: GET /api/vehicles/.../categories/... was
returning 404 from the ownership check for non-owner users.

Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
036ec80d60 chore: remove playwright repro scratch file
Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
81dd9c23b6 fix(vehicles): make decoded vehicles readable by any authenticated user
Decoded vehicle data is shared across the platform — once any user
decodes a VIN, the vehicle, its categories, and its parts should be
visible to every authenticated user. The previous getById enforced a
user↔vehicle junction via inner join, returning 404 "Araç bulunamadı"
when a user tried to view a vehicle they hadn't decoded themselves.
This surfaced as "Veriler yüklenirken bir hata oluştu" on the category
detail page for any vehicle the current user wasn't linked to.

Drop the ownership filter from getById; the userVehicles junction is
now used only for per-user history listing and delete operations.
Verified with Playwright: GET /api/vehicles/.../categories/... was
returning 404 from the ownership check for non-owner users.

Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
7bc92ced59 chore: remove playwright repro scratch file
Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
557080619e fix(vehicles): make decoded vehicles readable by any authenticated user
Decoded vehicle data is shared across the platform — once any user
decodes a VIN, the vehicle, its categories, and its parts should be
visible to every authenticated user. The previous getById enforced a
user↔vehicle junction via inner join, returning 404 "Araç bulunamadı"
when a user tried to view a vehicle they hadn't decoded themselves.
This surfaced as "Veriler yüklenirken bir hata oluştu" on the category
detail page for any vehicle the current user wasn't linked to.

Drop the ownership filter from getById; the userVehicles junction is
now used only for per-user history listing and delete operations.
Verified with Playwright: GET /api/vehicles/.../categories/... was
returning 404 from the ownership check for non-owner users.

Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
19fbc7c90f chore: remove playwright repro scratch file
Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
5d0580e5c9 fix(vehicles): make decoded vehicles readable by any authenticated user
Decoded vehicle data is shared across the platform — once any user
decodes a VIN, the vehicle, its categories, and its parts should be
visible to every authenticated user. The previous getById enforced a
user↔vehicle junction via inner join, returning 404 "Araç bulunamadı"
when a user tried to view a vehicle they hadn't decoded themselves.
This surfaced as "Veriler yüklenirken bir hata oluştu" on the category
detail page for any vehicle the current user wasn't linked to.

Drop the ownership filter from getById; the userVehicles junction is
now used only for per-user history listing and delete operations.
Verified with Playwright: GET /api/vehicles/.../categories/... was
returning 404 from the ownership check for non-owner users.

Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
823d51fc5a chore: remove playwright repro scratch file
Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
048d244883 fix(vehicles): make decoded vehicles readable by any authenticated user
Decoded vehicle data is shared across the platform — once any user
decodes a VIN, the vehicle, its categories, and its parts should be
visible to every authenticated user. The previous getById enforced a
user↔vehicle junction via inner join, returning 404 "Araç bulunamadı"
when a user tried to view a vehicle they hadn't decoded themselves.
This surfaced as "Veriler yüklenirken bir hata oluştu" on the category
detail page for any vehicle the current user wasn't linked to.

Drop the ownership filter from getById; the userVehicles junction is
now used only for per-user history listing and delete operations.
Verified with Playwright: GET /api/vehicles/.../categories/... was
returning 404 from the ownership check for non-owner users.

Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
3774fe36e3 chore: remove playwright repro scratch file
Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
a1efedb1d8 fix(vehicles): make decoded vehicles readable by any authenticated user
Decoded vehicle data is shared across the platform — once any user
decodes a VIN, the vehicle, its categories, and its parts should be
visible to every authenticated user. The previous getById enforced a
user↔vehicle junction via inner join, returning 404 "Araç bulunamadı"
when a user tried to view a vehicle they hadn't decoded themselves.
This surfaced as "Veriler yüklenirken bir hata oluştu" on the category
detail page for any vehicle the current user wasn't linked to.

Drop the ownership filter from getById; the userVehicles junction is
now used only for per-user history listing and delete operations.
Verified with Playwright: GET /api/vehicles/.../categories/... was
returning 404 from the ownership check for non-owner users.

Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
a3b7d3caff chore: remove playwright repro scratch file
Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
90e101875f fix(vehicles): make decoded vehicles readable by any authenticated user
Decoded vehicle data is shared across the platform — once any user
decodes a VIN, the vehicle, its categories, and its parts should be
visible to every authenticated user. The previous getById enforced a
user↔vehicle junction via inner join, returning 404 "Araç bulunamadı"
when a user tried to view a vehicle they hadn't decoded themselves.
This surfaced as "Veriler yüklenirken bir hata oluştu" on the category
detail page for any vehicle the current user wasn't linked to.

Drop the ownership filter from getById; the userVehicles junction is
now used only for per-user history listing and delete operations.
Verified with Playwright: GET /api/vehicles/.../categories/... was
returning 404 from the ownership check for non-owner users.

Fusion-Task-Id: FN-409
Fusion-Task-Lineage: eb556004-b8b9-4586-b1c3-c4577bf56036
2026-05-16 23:54:18 +00:00
Semih
cff689d34f chore: remove playwright repro scratch file
Fusion-Task-Id: FN-406
Fusion-Task-Lineage: 9616ae1a-9fa5-47cc-bf0d-3616eb4a0877
2026-05-16 23:54:18 +00:00
Semih
087f4b27cf fix(vehicles): make decoded vehicles readable by any authenticated user
Decoded vehicle data is shared across the platform — once any user
decodes a VIN, the vehicle, its categories, and its parts should be
visible to every authenticated user. The previous getById enforced a
user↔vehicle junction via inner join, returning 404 "Araç bulunamadı"
when a user tried to view a vehicle they hadn't decoded themselves.
This surfaced as "Veriler yüklenirken bir hata oluştu" on the category
detail page for any vehicle the current user wasn't linked to.

Drop the ownership filter from getById; the userVehicles junction is
now used only for per-user history listing and delete operations.
Verified with Playwright: GET /api/vehicles/.../categories/... was
returning 404 from the ownership check for non-owner users.

Fusion-Task-Id: FN-406
Fusion-Task-Lineage: 9616ae1a-9fa5-47cc-bf0d-3616eb4a0877
2026-05-16 23:54:18 +00:00
Semih
2cbb86e5de chore: remove playwright repro scratch file
Fusion-Task-Id: FN-406
Fusion-Task-Lineage: 9616ae1a-9fa5-47cc-bf0d-3616eb4a0877
2026-05-16 23:54:18 +00:00