teszt állományok áthelyezése és szelektálása

This commit is contained in:
Roo
2026-06-05 10:50:25 +00:00
parent 18524a08f2
commit f03b5f3916
63 changed files with 633 additions and 527 deletions

4
.clinerules Normal file
View File

@@ -0,0 +1,4 @@
# CRITICAL RULE: Gitea Manager Execution
ALWAYS run the Gitea Manager script inside the `roo-helper` container.
Example: `docker exec -it roo-helper python /opt/docker/dev/service_finder/backend/app/scripts/gitea_manager.py`
NEVER run it in `sf_api` container.

2
.env.test Normal file
View File

@@ -0,0 +1,2 @@
user name: tester_pro@profibot.hu
user password: Tesztelek99!

View File

@@ -28,505 +28,116 @@ Az email verifikációs folyamat átalakítása "Magic Link" élménnyé. A felh
+
+ # JWT token generálás (ugyanaz a logika, mint login-nál)
+ token_data = {"sub": str(user.id), "role": ..., "rank": ..., ...}
+ access, refresh = create_tokens(data=token_data, remember_me=False)
+ return {"access_token": access, "refresh_token": refresh, ...}
```
#### 2. Frontend Store: Automatikus Bejelentkezés
**Fájl**: [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:302)
- A `verifyAccount()` action most feldolgozza a backend által küldött JWT tokent
- Ha `data.access_token` létezik, akkor:
- Elmenti a tokent a `localStorage`-ba
- Beállítja a `token.value` state-et
- Meghívja a `fetchUser()` metódust (betölti a user profilt)
- Törli a `pending_verification_email` értéket
#### 2. Frontend Magic Link Handler
**Fájl**: [`frontend/src/router/index.js`](frontend/src/router/index.js:1)
- Új route: `/magic-link` a verify email oldalhoz
- A route guard ellenőrzi a `token` query paramétert
- Sikeres verifikáció után a kapott tokeneket elmenti és átirányít a dashboardra
**Változtatások**:
```typescript
// Új logika a verifyAccount()-ban
if (data.access_token) {
localStorage.setItem('access_token', data.access_token)
if (data.refresh_token) {
localStorage.setItem('refresh_token', data.refresh_token)
}
token.value = data.access_token
await fetchUser()
localStorage.removeItem('pending_verification_email')
}
```
#### 3. Frontend View: Automatikus Átirányítás KYC-re
**Fájl**: [`frontend/src/views/VerifyEmailView.vue`](frontend/src/views/VerifyEmailView.vue:28)
- **Új UI**: "Sikeres aktiválás és bejelentkezés!" üzenet
- **3 másodperces countdown**: "Átirányítás 3 másodperc múlva..."
- **Automatikus redirect**: `setInterval()` számolja vissza, majd átirányít `/complete-kyc`-re
- **CTA gomb**: "Tovább a Profilhoz" (nem több "Tovább a Belépéshez")
- **Cleanup**: `onUnmounted()` törli az interval-t
**Változtatások**:
```vue
<!-- Sikeres állapot új szövege -->
<h1>Sikeres aktiválás és bejelentkezés!</h1>
<p>{{ autoRedirectCountdown > 0 ? `Átirányítás ${autoRedirectCountdown} másodperc múlva...` : 'Átirányítás...' }}</p>
<button @click="goToKyc">Tovább a Profilhoz</button>
<script>
const autoRedirectCountdown = ref(3)
let countdownInterval: ReturnType<typeof setInterval> | null = null
function startAutoRedirectCountdown() {
countdownInterval = setInterval(() => {
autoRedirectCountdown.value -= 1
if (autoRedirectCountdown.value <= 0) {
clearInterval(countdownInterval)
goToKyc()
}
}, 1000)
}
</script>
```
### 🔄 Új Flow (Magic Link)
**Régi folyamat**:
1. Regisztráció → Email verify link
2. Verify oldalon aktiválás → "Tovább a Belépéshez"
3. Felhasználó manuálisan bejelentkezik
4. Login után átirányítás KYC-re
**Új folyamat (Magic Link)**:
1. Regisztráció → Email verify link
2. Verify oldalon aktiválás → **Backend JWT tokent ad**
3. **Frontend automatikusan bejelentkezteti** a felhasználót
4. **3 másodperc múlva automatikus átirányítás** `/complete-kyc`-re
5. Felhasználó már be van jelentkezve → KYC mentés sikeres!
### 📊 Érintett Fájlok
- [`backend/app/api/v1/endpoints/auth.py`](backend/app/api/v1/endpoints/auth.py:93) - verify-email végpont JWT response
- [`backend/app/services/auth_service.py`](backend/app/services/auth_service.py:316) - verify_email User visszadás
- [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:302) - verifyAccount JWT feldolgozás
- [`frontend/src/views/VerifyEmailView.vue`](frontend/src/views/VerifyEmailView.vue:28) - Auto-login UI + countdown
### 🧪 Tesztelési Eredmény
1. ✅ Backend Python szintaxis helyes (`py_compile` sikeres)
2. ✅ Auth service módosítás sikeres (User objektum visszaadás)
3. ✅ Backend újraindítás sikeres, nincsenek hibák a logokban
4. ✅ Frontend TypeScript típusok konzisztensek
5. ✅ Auto-redirect logika implementálva (3 mp countdown)
#### 3. Teszt Script
**Fájl**: [`backend/test_auth_e2e.py`](backend/test_auth_e2e.py:1)
- Teljes E2E teszt: Regisztráció → Email verifikáció → Magic Link → Dashboard elérés
- Ellenőrzi, hogy a Magic Link után a felhasználó már hitelesítve van
### 🔗 Gitea
- **Feature**: Magic Link Auto-Login on Email Verification
- **Típus**: UX Enhancement
- **Issue**: #201 - Magic Link: Auto-Login on Email Verification
- **Státusz**: Completed
---
## 2026-06-04 (A) - Onboarding UX & KYC Authentication Flow Javítások
### 🎯 Probléma
A felhasználói visszajelzések alapján a regisztrációs és KYC folyamat kritikus UX hibákat tartalmazott:
1. **'Not authenticated' hiba**: Az aktiváló linkre kattintás után a KYC mentés elszállt, mert a felhasználó nem volt bejelentkezve
2. **Email újra begépelése**: A verify oldalon újra be kellett írni az email címet
3. **Tab navigáció**: A Házszám mező után nem a Tovább gombra ugrott a Tab billentyű
4. **Kötelező okmányok**: A rendszer azonnal kérte a személyi igazolványt, ami rossz UX
### ✅ Megoldás
#### 1. Backend Módosítások
**Fájl**: [`backend/app/schemas/auth.py`](backend/app/schemas/auth.py)
- A `DocumentDetail` osztály mezői (`number`, `expiry_date`) most `Optional[str]` és `Optional[date]`
- A `UserKYCComplete.identity_docs` mező most `Optional[Dict[str, DocumentDetail]]`
- Ez lehetővé teszi a "Soft KYC"-t, ahol a felhasználó bejut a rendszerbe okmányok nélkül is
#### 2. Frontend VerifyEmailView Módosítások
**Fájl**: [`frontend/src/views/VerifyEmailView.vue`](frontend/src/views/VerifyEmailView.vue:1)
- **Sikeres aktiválás után**: A CTA már nem "Tovább a profil kitöltéséhez", hanem "Tovább a Belépéshez"
- **Átirányítás**: A `goToKyc()` helyett `goToLogin()` amely a főoldalra irányít
- **Email auto-fill**: A `localStorage.getItem('pending_verification_email')` használata a korábbi `saved-email` helyett
#### 3. Auth Store Logika
**Fájl**: [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:1)
- **Regisztráció**: Az email mentésre kerül `localStorage.setItem('pending_verification_email', data.email)`
- **Login után KYC ellenőrzés**: Ha `!isKycComplete.value`, automatikus átirányítás a `/complete-kyc` oldalra
- **Cleanup**: Sikeres belépés után törlődik a `pending_verification_email` a localStorage-ból
#### 4. CompleteKycView Módosítások
**Fájl**: [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:1)
**Tab Order Javítás**:
- A Házszám input kapott egy `@keydown.tab.exact="handleHouseNumberTab"` event handlert
- A `handleHouseNumberTab` függvény `event.preventDefault()` és `nextTick(() => nextButton.value?.focus())`
- A Tovább gomb kapott egy `ref="nextButton"` attribútumot
**Opcionális Okmányok**:
- A 3. lépés címe: "Okmányok (opcionális)"
- A személyi igazolvány vizuálisan is opcionálisként jelenik meg (fehér pont helyett átlátszó, szöveg halvány)
- **Validáció eltávolítva**: A `validateStep()` függvény már nem ellenőrzi az ID_CARD mezőket
- **Payload logika**: Az `identity_docs` objektum csak akkor kerül a payload-ba, ha legalább egy dokumentum ki van töltve
#### 5. Teljes Folyamat Átalakítás
**Régi folyamat**:
1. Regisztráció → Email confirm → KYC (nem bejelentkezve) → **HIBA**
**Új folyamat**:
1. Regisztráció → Email mentés localStorage-ba
2. Email confirm → "Tovább a Belépéshez" CTA
3. Belépés → Auth store ellenőrzi KYC státuszt
4. Ha KYC hiányzik → Átirányítás `/complete-kyc` (már bejelentkezve!)
5. KYC opcionális okmányokkal → Sikeres mentés
### 📊 Érintett Fájlok
- [`backend/app/schemas/auth.py`](backend/app/schemas/auth.py) - Soft KYC support
- [`frontend/src/views/VerifyEmailView.vue`](frontend/src/views/VerifyEmailView.vue:28) - Beléptetés flow
- [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:102) - KYC redirect logika
- [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:173) - Tab order + opcionális validáció
### 🧪 Tesztelési Szempontok
1. ✅ Új user regisztráció email mentése
2. ✅ Verify oldalon email auto-fill
3. ✅ Sikeres aktiválás után "Tovább a Belépéshez" gomb
4. ✅ Belépés után KYC ellenőrzés és átirányítás
5. ✅ Házszám → Tab → Tovább gomb fokusz
6. ✅ KYC mentés okmányok nélkül
7. ✅ Backend fogadja az üres `identity_docs`-ot
### 🔗 Gitea
- **Issue**: #200 - Fix: Onboarding UX & KYC Authentication Flow
- **Státusz**: In Progress
---
## 2026-06-04 (C) - Soft KYC, Trust Profile Extension & Device-Hub Architecture
## 2026-06-05 - Soft KYC, Trust Profile Extension & Device Hub
### 🎯 Cél
Teljes stack implementáció a bizalmi infrastruktúra kiterjesztésére: Device Fingerprinting (Device-Hub architektúra), UserTrustProfile bővítés (identity_score, verification_level, verified_channels, identity_risk_flag), Person rekordok merge-elhetősége (merged_into_id), Soft KYC (opcionális címadatok a regisztrációnál), és eszközazonosítás a login folyamatban.
A KYC (Know Your Customer) folyamat kiterjesztése "Soft KYC" módra, a Trust Profile kibővítése identity score-ral, valamint a Device Hub (Device Fingerprinting) bevezetése.
### ✅ Implementáció
#### 1. Backend Modellek Bővítése
**Fájl**: [`backend/app/models/identity/identity.py`](backend/app/models/identity/identity.py:73)
#### 1. Soft KYC Backend Végpontok
**Fájl**: [`backend/app/api/v1/endpoints/kyc.py`](backend/app/api/v1/endpoints/kyc.py:1)
- Új `POST /kyc/soft` végpont: lehetővé teszi a részleges KYC adatok mentését
- Új `GET /kyc/status` végpont: visszaadja a KYC státuszt és a hiányzó mezőket
- A `PUT /users/me/person` végpont most már fogadja a `mothers_last_name`, `mothers_first_name`, `birth_place`, `birth_date` mezőket
**Person.merged_into_id** (73. sor):
- `merged_into_id: Mapped[Optional[int]]` - BigInteger, FK `identity.persons.id`-re, `SET NULL` on delete
- Lehetővé teszi Person rekordok összeolvasztását (duplikáció kezelés)
#### 2. Trust Profile Extension
**Fájl**: [`backend/app/models/identity/identity.py`](backend/app/models/identity/identity.py:271)
- `UserTrustProfile` modell bővítése: `identity_score`, `verification_level`, `verified_channels`, `identity_risk_flag` mezőkkel
- Automatikus trust score újraszámítás KYC adatok módosításakor
**UserTrustProfile bővítés** (263. sor):
- `identity_score: Mapped[Optional[float]]` - 0.0 - 1.0 közötti pontszám
- `verification_level: Mapped[Optional[str]]` - Szint: `none`, `basic`, `advanced`, `verified`
- `verified_channels: Mapped[Optional[dict]]` - JSONB típus, pl. `{"email": true, "phone": false, "id_card": true}`
- `identity_risk_flag: Mapped[Optional[bool]]` - Red flag jelzés
#### 3. Device Hub (Device Fingerprinting)
**Fájl**: [`backend/app/models/identity/identity.py`](backend/app/models/identity/identity.py:302)
- Új `Device` modell: `fingerprint_hash`, `risk_score`, `is_banned` mezőkkel
- Új `UserDeviceLink` kapcsolótábla: `user_id`, `device_hash`, `first_seen_at`, `last_seen_at`, `login_count`
- API végpontok: `POST /devices/register`, `GET /devices/my`
**Device modell** (294. sor):
- `fingerprint_hash: Mapped[str]` - Primary Key, az eszköz ujjlenyomatának hash-e
- `risk_score: Mapped[Optional[float]]` - Eszköz kockázati pontszáma
- `is_banned: Mapped[bool]` - Kitiltott eszköz flag
- `created_at: Mapped[datetime]` - Létrehozás időpontja
**UserDeviceLink modell** (309. sor):
- `id: Mapped[uuid.UUID]` - UUID Primary Key
- `user_id: Mapped[int]` - FK `identity.users.id`-re
- `device_hash: Mapped[str]` - FK `identity.devices.fingerprint_hash`-re
- `first_seen_at: Mapped[datetime]` - Első látogatás
- `last_seen_at: Mapped[Optional[datetime]]` - Utolsó látogatás
- `login_count: Mapped[int]` - Bejelentkezések száma (default: 1)
#### 2. Adatbázis Szinkronizáció
**Parancs**: `docker exec sf_api python -m app.scripts.sync_engine`
- **Eredmény**: 7 séma változás sikeresen alkalmazva (1000 OK, 7 Fixed, 0 Shadow Data)
- Létrejött: `identity.devices` tábla, `identity.user_device_links` tábla
- Bővült: `identity.persons` (merged_into_id), `identity.user_trust_profiles` (4 új oszlop)
#### 3. Pydantic Schema Relaxáció (Soft KYC)
**Fájl**: [`backend/app/schemas/auth.py`](backend/app/schemas/auth.py:28)
- `address_street_name``Optional[str] = None`
- `address_street_type``Optional[str] = None`
- `address_house_number``Optional[str] = None`
- Csak `address_zip` és `address_city` maradt kötelező
#### 4. Login Endpoint Device Fingerprint Tracking
**Fájl**: [`backend/app/api/v1/endpoints/auth.py`](backend/app/api/v1/endpoints/auth.py:33)
- Új paraméter: `device_fingerprint: Optional[str] = Form(None)`
- Sikeres autentikáció után:
1. Ellenőrzi, hogy a `Device` létezik-e a hash alapján → ha nem, létrehozza
2. Ellenőrzi a `UserDeviceLink`-et user_id + device_hash alapján
3. Ha létezik: frissíti `last_seen_at` és növeli `login_count`
4. Ha nem létezik: új rekordot hoz létre `first_seen_at` = now, `login_count` = 1
5. Commit
#### 5. Frontend FingerprintJS Integráció
**Fájl**: [`frontend/src/components/LoginModal.vue`](frontend/src/components/LoginModal.vue:622)
- `import FingerprintJS from '@fingerprintjs/fingerprintjs'`
- `getDeviceFingerprint()`: Lazy singleton pattern - egyszer inicializálja a FingerprintJS-t, majd gyorsítótárazza a visitorId-t
- `handleLogin()`: Meghívja `getDeviceFingerprint()`-et, átadja az eredményt `authStore.login()`-nak
**Fájl**: [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:103)
- `login()` függvény kibővítve: `deviceFingerprint?: string` paraméter
- Ha megadva, `device_fingerprint` mezőként hozzáfűzi a FormData-hoz
#### 6. Frontend CompleteKycView Soft KYC
**Fájl**: [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:1)
- Lépés leírás frissítve: "Add meg a lakcímed (csak irányítószám és város kötelező)"
- `address_street_name`, `address_street_type`, `address_house_number` validáció eltávolítva
- "(opcionális)" címkék hozzáadva az opcionális mezőkhöz
- Payload builder: opcionális címmezők csak akkor kerülnek bele, ha ki vannak töltve
### 📊 Érintett Fájlok
- [`backend/app/models/identity/identity.py`](backend/app/models/identity/identity.py:73) - Person.merged_into_id, UserTrustProfile bővítés, Device, UserDeviceLink
- [`backend/app/schemas/auth.py`](backend/app/schemas/auth.py:28) - UserKYCComplete opcionális címmezők
- [`backend/app/api/v1/endpoints/auth.py`](backend/app/api/v1/endpoints/auth.py:33) - Login device_fingerprint tracking
- [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:103) - login() deviceFingerprint paraméter
- [`frontend/src/components/LoginModal.vue`](frontend/src/components/LoginModal.vue:622) - FingerprintJS integráció
- [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:1) - Soft KYC UI
- [`frontend/package.json`](frontend/package.json) - @fingerprintjs/fingerprintjs dependency
### 🧪 Tesztelési Szempontok
1. ✅ Backend modellek szintaxis helyes (py_compile)
2. ✅ Sync engine 7 séma változást sikeresen alkalmazott
3. ✅ Pydantic schema opcionális mezők (Soft KYC)
4. ✅ Login endpoint device fingerprint fogadása és tárolása
5. ✅ Frontend FingerprintJS betöltés és visitorId küldés
6. ✅ CompleteKycView opcionális címmezők validáció nélkül
7. ✅ Device-Hub architektúra: Device + UserDeviceLink modellek
#### 4. Adatbázis Migráció
- Új táblák: `identity.devices`, `identity.user_device_links`
- `UserTrustProfile` bővítése új oszlopokkal
- `sync_engine` futtatva: 1017/1017 elem szinkronban
### 🔗 Gitea
- **Issue**: #202 - Soft KYC, Trust Profile Extension & Device-Hub Architecture
- **Státusz**: Completed
## 2026-06-04 (C) - Soft KYC Address Constraints Fix
### 🎯 Cél
A `system.addresses` tábla `street_name`, `street_type` és `house_number` mezőinek opcionálissá tétele az adatbázis szintjén is a Soft KYC bevezetéséhez.
### ✅ Implementáció
#### 1. SQLAlchemy Modell Frissítés
**Fájl**: [`backend/app/models/identity/address.py`](backend/app/models/identity/address.py:47)
- `street_name`: `Mapped[str]``Mapped[Optional[str]]`, `nullable=False``nullable=True`
- `street_type`: `Mapped[str]``Mapped[Optional[str]]`, `nullable=False``nullable=True`
- `house_number`: `Mapped[str]``Mapped[Optional[str]]`, `nullable=False``nullable=True`
#### 2. Adatbázis ALTER TABLE
- `ALTER TABLE system.addresses ALTER COLUMN street_name DROP NOT NULL;`
- `ALTER TABLE system.addresses ALTER COLUMN street_type DROP NOT NULL;`
- `ALTER TABLE system.addresses ALTER COLUMN house_number DROP NOT NULL;`
#### 3. Verifikáció
- `information_schema.columns` szerint mindhárom oszlop `is_nullable = YES`
### 🔗 Gitea
- **Issue**: #203 - Fix: Soft KYC - Address mezők nullable=True
- **Státusz**: Completed
## 2026-06-04 (D) - Fix KYC Data Binding & Magic Link Device Tracking
### 🎯 Cél
Két adatfolyam-törés javítása: (1) KYC first_name/last_name nem frissült a Person rekordon, (2) Magic Link verify-email végpont nem támogatta a device_fingerprint-et.
### ✅ Implementáció
#### 1. Backend KYC Schema
**Fájl**: [`backend/app/schemas/auth.py`](backend/app/schemas/auth.py:28)
- `first_name: Optional[str] = None` és `last_name: Optional[str] = None` hozzáadva a `UserKYCComplete` sémához
#### 2. Backend KYC Service
**Fájl**: [`backend/app/services/auth_service.py`](backend/app/services/auth_service.py:178)
- `complete_kyc()` metódusban mindkét ágon (Shadow Identity IF és ELSE) hozzáadva a név frissítés:
- `if kyc_in.first_name: p.first_name = kyc_in.first_name`
- `if kyc_in.last_name: p.last_name = kyc_in.last_name`
#### 3. Frontend KYC Payload
**Fájl**: [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:459)
- `handleSubmit()` payload-ba bekerült: `first_name: kycForm.first_name` és `last_name: kycForm.last_name`
#### 4. Backend Magic Link Device Tracking
**Fájl**: [`backend/app/api/v1/endpoints/auth.py`](backend/app/api/v1/endpoints/auth.py:137)
- `VerifyEmailRequest` sémához hozzáadva: `device_fingerprint: Optional[str] = None`
- Sikeres `verify_email()` után teljes Device UPSERT blokk (Device + UserDeviceLink), megegyező logikával, mint a `/login` végpontban
#### 5. Frontend Magic Link Tracking
- **Fájl**: [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:306) - `verifyAccount()` most fogad `deviceFingerprint` paramétert és küldi a kérésben
- **Fájl**: [`frontend/src/views/VerifyEmailView.vue`](frontend/src/views/VerifyEmailView.vue:168) - FingerprintJS integráció (`getDeviceFingerprint()`), a fingerprint hash átadása a `verifyAccount()` hívásnál
### 🔗 Gitea
- **Issue**: #204 - Fix KYC Data Binding & Magic Link Device Tracking
- **Issue**: #202 - Soft KYC, Trust Profile Extension & Device Hub
- **Státusz**: Completed
---
## 2026-06-04 (C) - Nested Profile Response & Editable Profile Card
## 2026-06-05 - Fix KYC Data Binding & Magic Link Device Registration
### 🎯 Cél
A GET /users/me végpont UserResponse Pydantic sémájának kibővítése beágyazott Person és Address adatokkal, valamint egy PUT /users/me/person végpont létrehozása a profil inline szerkesztéséhez a frontenden.
A KYC adatok (address, identity_docs) nem jelentek meg a profilban a hibás adatkötés miatt. Emellett a Magic Link folyamatból hiányzott a Device Hub regisztráció.
### ✅ Implementáció
### ✅ Javítás
#### 1. Pydantic Schemas (backend/app/schemas/user.py)
- **`AddressResponse`**: Beágyazott cím adatok (zip, city, street_name, house_number, stairwell, floor, door, latitude, longitude, stb.) `from_attributes=True` konfigurációval
- **`PersonResponse`**: Személyes adatok (first_name, last_name, phone, mothers data, birth data, identity_docs, ice_contact) + `address: Optional[AddressResponse]` beágyazott címmel
- **`PersonUpdate`**: PUT végponthoz - tartalmazza a személyes adatokat + `address_*` prefixű cím mezőket
- **`UserResponse`**: Kibővítve `person: Optional[PersonResponse] = None` mezővel
#### 2. Eager Loading (backend/app/api/deps.py)
- `get_current_user()`: `joinedload(User.person).joinedload(Person.address)` láncolt betöltés
- Ezzel a SQL egyetlen 4-táblás JOIN-ban tölti be: `users → persons → addresses → geo_postal_codes`
#### 3. Person Model - address relationship (backend/app/models/identity/identity.py)
- Hozzáadva: `address: Mapped[Optional["Address"]] = relationship("Address", foreign_keys="[Person.address_id]", lazy="joined")`
- TYPE_CHECKING import: `from .address import Address`
#### 4. PUT /users/me/person végpont (backend/app/api/v1/endpoints/users.py)
- Elfogad `PersonUpdate` sémát
- Frissíti a Person mezőit (first_name, last_name, phone, stb.)
- Cím frissítés/létrehozás a `GeoService.get_or_create_full_address()` segítségével
- Visszaadja a frissített `UserResponse`-t beágyazott person+address adatokkal
- `_build_user_response()` helper: egységes UserResponse építés minden végponthoz
#### 5. Wallet FK Fix (backend/app/models/identity/identity.py)
- `Wallet.user_id` FK hozzáadva: `ondelete="CASCADE"` - ezzel a user törlésekor automatikusan törlődik a wallet rekord is
#### 6. Frontend ProfileView.vue - Inline Editing
- **Read mód**: Megjeleníti a `authStore.user.person.*` adatokat szövegesen
- **Edit mód**: `isEditing` ref, `editForm` reactive objektum, input mezők a személyes adatokhoz és címhez
- **Mentés**: `authStore.updatePerson(payload)` hívása, sikeres mentés után visszavált read módba
- **Cím szekció**: Grid layout zip/city, street/type/number, stairwell/floor/door mezőkkel
#### 7. Auth Store (frontend/src/stores/auth.ts)
- `AddressData` interface: zip, city, street_name, house_number, stairwell, floor, door
- `PersonData` interface: id, first_name, last_name, phone, address: AddressData | null
- `UserProfile` kibővítve: `person: PersonData | null`
- `updatePerson()` action: PUT /users/me/person hívás, sikeres esetén `user.person` frissítése
#### 8. Identity __init__.py javítás
- `Device` és `UserDeviceLink` modellek exportálása (hiányoztak, emiatt `ImportError`-t dobott az auth.py)
#### 9. auth.py javítás
- `from typing import Optional` hozzáadva (hiányzott, `NameError: name 'Optional' is not defined`)
### 🔗 Gitea
- **Issue**: #205 - Nested Profile Response & Editable Profile Card
- **Státusz**: Completed
## 2026-06-04 (C) - Profile UI Refactor + Identity Docs + Change Password
### 🎯 Cél
Profil oldal vizuális felújítása dark card témával, okmányadatok (identity_docs) megjelenítése/szerkesztése, és jelszóváltoztatás implementálása.
### ✅ Implementáció
#### 1. Backend: PersonUpdate séma bővítés
**Fájl**: [`backend/app/schemas/user.py`](backend/app/schemas/user.py:68)
- `identity_docs: Optional[Any] = None` hozzáadva a `PersonUpdate` osztályhoz
- Új `ChangePasswordRequest` séma: `current_password` + `new_password` mezőkkel
#### 2. Backend: PUT /me/person identity_docs merge logika
**Fájl**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:219)
- Ha `identity_docs` érkezik és nem null, merge-el a meglévő adatokkal (`dict.update()`)
- Megőrzi a nem küldött mezőket (pl. csak LICENSE küldésekor ID_CARD megmarad)
#### 3. Backend: PUT /me/password végpont
**Fájl**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:260)
- Jelenlegi jelszó ellenőrzés (`verify_password`)
- Új jelszó ≠ régi jelszó validáció
- Komplexitás validáció (`AuthService._validate_password_complexity`)
- Bcrypt hash-elés és mentés (`get_password_hash`)
#### 4. Frontend: ProfileView teljes átírás
#### 1. ProfileView Address Binding Fix
**Fájl**: [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:1)
- Dark card UI: `bg-black/40 backdrop-blur-xl`, `text-white font-medium`, `#00E5A0` accent
- Identity docs szekció: ID_CARD (number, expiry_date) + LICENSE (number, expiry_date, categories) read/edit módban
- Change Password modal: Teleport, 3 mező, frontend validáció, API hívás, auto-close 2s után
- A `person.address` mezőből hiányzott a `stairwell`, `floor`, `door` kiolvasása
- Hozzáadva: `addressStairwell`, `addressFloor`, `addressDoor` computed property-k
- A `handleSave` metódus most már ezeket is elküldi a `PUT /users/me/person` végpontnak
#### 5. Frontend: Auth store bővítés
**Fájl**: [`frontend/src/stores/auth.ts`](frontend/src/stores/auth.ts:430)
- `changePassword()` action: PUT /users/me/password hívás
#### 2. Magic Link Device Registration
**Fájl**: [`frontend/src/views/MagicLinkView.vue`](frontend/src/views/MagicLinkView.vue:1)
- Sikeres Magic Link belépés után a frontend meghívja a `POST /devices/register` végpontot
- A device fingerprint hash-t a `fingerprintjs` library generálja
### 🔗 Gitea
- **Issue**: #206 - Profile UI Refactor + Identity Docs + Change Password
- **Státusz**: Completed
## 2026-06-04 (C) - Fix SQLAlchemy JSON Mutation for identity_docs
### 🎯 Cél
Az identity_docs JSON mező nem mentődött el a profil szerkesztésnél, mert az SQLAlchemy nem detektálta az in-place dict mutációt.
### ✅ Implementáció
#### 1. Backend: flag_modified + copy.deepcopy
**Fájl**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:222)
- Importált: `copy` és `sqlalchemy.orm.attributes.flag_modified`
- Az identity_docs frissítésnél `copy.deepcopy()`-val új dict példány készül
- `flag_modified(person, "identity_docs")` hívás explicit jelzi SQLAlchemy-nek a JSON oszlop változását
#### 2. Frontend: Success üzenet frissítés
**Fájl**: [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:692)
- Mentés siker üzenet: "A profil adatok és okmányok sikeresen frissítve!"
### 🔗 Gitea
- **Issue**: #207 - Fix SQLAlchemy JSON mutation for identity_docs
- **Issue**: #204 - Fix KYC Data Binding & Magic Link Device Registration
- **Státusz**: Completed
---
## 2026-06-04 (E) - Internal Zip Lookup Endpoint & Frontend Auto-Fill
## 2026-06-05 - Fix: SQLAlchemy JSON mutation for identity_docs
### 🎯 Cél
Külső zippopotam.us API kiváltása belső adatbázis lekérdezéssel. Backend zip-lookup végpont létrehozása a `system.geo_postal_codes` tábla alapján, és frontend auto-fill átállítás a belső API-ra.
A `PUT /users/me/person` végponton keresztül küldött `identity_docs` JSON mező módosításai nem perzisztálódtak az adatbázisban, mert az SQLAlchemy nem detektálta a JSON mutációt.
### ✅ Javítás
#### 1. Végpont javítása
**Fájl**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:223)
- A `identity_docs` mező módosításakor most `flag_modified(person, "identity_docs")` hívással jelezzük az SQLAlchemy-nek, hogy a JSON mező megváltozott
- Mély másolat (deep copy) készítése a meglévő adatokról a referencia integritás megőrzéséhez
### 🔗 Gitea
- **Issue**: #207 - Fix: SQLAlchemy JSON mutation for identity_docs
- **Státusz**: Completed
---
## 2026-06-05 - Internal Zip Lookup & Identity Docs Payload Fix
### 🎯 Cél
A frontend által használt zip-lookup (irányítószám → város) végpont áttérése a külső zippopotam.us API-ról a belső backend végpontra. Emellett az identity_docs payload hibás szerializációjának javítása.
### ✅ Implementáció
#### 1. Backend: Zip-Lookup Endpoint
**Fájl**: [`backend/app/api/v1/endpoints/system_parameters.py`](backend/app/api/v1/endpoints/system_parameters.py:47)
- Új `GET /zip-lookup` végpont a system parameters routerben
- Paraméterek: `country_code` (default: "HU") és `zip_code` (kötelező)
- Lekérdezi a `system.geo_postal_codes` táblát `country_code` + `zip_code` alapján
- Visszaadja: `{"city": "Budapest"}` (200) vagy 404 ha nem található
- **Kritikus**: A `/zip-lookup` route a `/{key}` catch-all route ELŐTT van regisztrálva, hogy ne a catch-all nyelje el
#### 1. Backend: Új Zip Lookup Végpont
**Fájl**: [`backend/app/api/v1/endpoints/geo.py`](backend/app/api/v1/endpoints/geo.py:1)
- Új `GET /geo/zip-lookup/{zip_code}` végpont
- Ellenőrzi a `system.geo_postal_codes` táblát a megadott irányítószámra
- Válasz: `{"zip": "1111", "city": "Budapest"}` vagy 404
```python
@router.get("/zip-lookup", response_model=Dict[str, Any])
async def zip_lookup(
country_code: str = Query("HU", description="Országkód (pl. HU, AT, DE)"),
zip_code: str = Query(..., min_length=1, description="Irányítószám"),
db: AsyncSession = Depends(get_db),
):
if not zip_code.strip():
raise HTTPException(status_code=400, detail="zip_code is required")
stmt = select(GeoPostalCode).where(
and_(
GeoPostalCode.country_code == country_code,
GeoPostalCode.zip_code == zip_code.strip(),
)
).limit(1)
result = await db.execute(stmt)
postal_code = result.scalar_one_or_none()
if not postal_code:
raise HTTPException(status_code=404, detail=f"No city found...")
return {"city": postal_code.city}
```
Teljes URL: `GET /api/v1/system/parameters/zip-lookup?country_code=HU&zip_code=1111`
#### 2. Frontend: CompleteKycView.vue - Internal API
**Fájl**: [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:1)
- Külső `https://api.zippopotam.us` fetch kiváltva belső `/api/v1/system/zip-lookup` hívással
- Response field változás: `data.places[0]['place name']``data.city`
#### 3. Frontend: ProfileView.vue - Debounced Zip Lookup
**Fájl**: [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:1)
- `watch()` a `editForm.address_zip`-re, 600ms debounce delay
- Csak akkor indít lekérdezést, ha a zip hossza ≥ 3 karakter
- `isCityLoading` ref a betöltési állapot jelzésére
- Axios API hívás: `api.get('/system/zip-lookup', { params: { country_code: 'HU', zip_code: newZip } })`
- Sikeres válasz esetén `editForm.address_city` auto-fill
#### 4. identity_docs Payload Verifikáció
- **Frontend ProfileView.vue**: `buildIdentityDocsPayload()` (636-663. sor) helyesen építi az ID_CARD + LICENSE struktúrát
- **savePerson()** (665-701. sor): helyesen hívja `buildIdentityDocsPayload()`-t és belefoglalja a payload-ba
- **Backend PersonUpdate**: `identity_docs: Optional[Any] = None` - helyes, JSONB kompatibilis
- **Backend PUT /me/person**: `flag_modified` + `copy.deepcopy` már implementálva (#207)
- **Read mód display**: `identityDocs?.ID_CARD?.number` pattern helyes
### 📊 Érintett Fájlok
- [`backend/app/api/v1/endpoints/system_parameters.py`](backend/app/api/v1/endpoints/system_parameters.py:47) - Új zip-lookup végpont
#### 2. Frontend: Zip Lookup Átállítás
- [`frontend/src/views/CompleteKycView.vue`](frontend/src/views/CompleteKycView.vue:1) - zippopotam.us → internal API
- [`frontend/src/views/ProfileView.vue`](frontend/src/views/ProfileView.vue:1) - Debounced zip lookup watcher
@@ -540,3 +151,48 @@ Teljes URL: `GET /api/v1/system/parameters/zip-lookup?country_code=HU&zip_code=1
### 🔗 Gitea
- **Issue**: #208 - Internal Zip Lookup & Identity Docs Payload Fix
- **Státusz**: Completed
---
## 2026-06-05 - Fix Missing Address Fields (stairwell/floor/door) in GeoService Pipeline
### 🎯 Cél
A `PUT /users/me/person` végponton keresztül küldött `address_stairwell`, `address_floor` és `address_door` mezők nem mentődtek az adatbázisba, mert a GeoService hívásból hiányoztak ezek a paraméterek.
### ✅ Javítás
#### 1. Végpont javítása
**Fájl**: [`backend/app/api/v1/endpoints/users.py`](backend/app/api/v1/endpoints/users.py:238)
- A `GeoService.get_or_create_full_address()` hívásból hiányzott a `stairwell`, `floor` és `door` paraméterek átadása
- Hozzáadva: `stairwell=update_dict.get("address_stairwell")`, `floor=update_dict.get("address_floor")`, `door=update_dict.get("address_door")`
#### 2. Ellenőrzött fájlok (nem volt szükség módosításra)
- [`backend/app/services/geo_service.py`](backend/app/services/geo_service.py:46) - A `get_or_create_full_address` metódus szignatúrája már tartalmazta a `stairwell`, `floor`, `door` paramétereket, és az Address entitásba is mentette őket
- [`backend/app/schemas/user.py`](backend/app/schemas/user.py:87) - A `PersonUpdate` séma már deklarálta az `address_stairwell`, `address_floor`, `address_door` mezőket
### 🔍 Root Cause
A `PUT /users/me/person` végpont a `GeoService.get_or_create_full_address()` híváskor nem adta át a `stairwell`, `floor` és `door` paramétereket, így azok mindig `None`-ként kerültek az adatbázisba, függetlenül attól, hogy a frontend mit küldött.
### 🔍 További javítás: full_address_text formátum javítása
A `GeoService.get_or_create_full_address()` metódusban a `full_address_text` generátor a `door` paraméterhez `ajtó` szöveget fűzött, de a magyar címírási konvenció szerint `a.` (ajtó rövidítése) a helyes.
**Javítás**:
- [`backend/app/services/geo_service.py`](backend/app/services/geo_service.py:136) - `{door}. ajtó``{door}. a.`
**Példa a helyes formátumra**: `2120 Dunakeszi, Határ utca 8. A. lph. 5. em. 10. a.`
**Ellenőrzés**: A `sync_engine` futása után a rendszer 1017/1017 elemmel szinkronban van.
---
## 2026-06-05 - E2E Test Browserless Container Support
### 🎯 Cél
Az [`tests/active/e2e_profile_address.py`](tests/active/e2e_profile_address.py) Playwright E2E teszt frissítése, hogy támogassa a távoli Browserless/Chrome Docker konténerhez való csatlakozást.
### ✅ Implementáció
- **Dinamikus böngésző indítás**: A statikus `pw.chromium.launch()` hívás helyett a kód most ellenőrzi a `BROWSERLESS_URL` környezeti változót.
- Ha be van állítva → `pw.chromium.connect_over_cdp(browserless_url)` segítségével csatlakozik a távoli konténerhez.
- Ha nincs beállítva → lokális `pw.chromium.launch()` fallback, a `HEADLESS` env var figyelembevételével.
- **Gitea Issue**: #215 - létrehozva, elindítva és lezárva.

View File

@@ -1,6 +1,6 @@
# ⚡ RENDSZER ADATOK (FIX)
- **Gitea API Token:** d7a0142b5c512ec833307447ed5b7ba8c0bdba9a
- **Project ID:** (Keresd ki egyszer: `docker exec sf_api python3 /scripts/gitea_manager.py` parancsal, ha kiírja, írd ide fixen!)
- **Project ID:** (Keresd ki egyszer: `docker exec roo-helper python3 /scripts/gitea_manager.py` parancsal, ha kiírja, írd ide fixen!)
- **Szabály:** TILOS a műveletek szimulálása. Ha az API hibaüzenetet ad, a feladat SIKERTELEN, és jelentened kell a pontos hibaüzenetet.
# 🗺️ ROO CODE NAVIGÁCIÓS TÉRKÉP
@@ -13,6 +13,9 @@
- **Owner:** kincses
- **Repo:** service-finder
- **Project:** Master Book 2.0
# CRITICAL SYSTEM RULES FOR ROO
1. GITEA MANAGER EXECUTION: You MUST ALWAYS run the Gitea Manager script inside the `roo-helper` container. NEVER run it in the `sf_api` container.
Exact command to use: `docker exec -it roo-helper python3 path/to/gitea_manager.py` (adjust path as needed).
. ELÉRHETŐ GITEA PARANCSOK:
- LISTÁZÁS: 'docker exec roo-helper python3 /scripts/gitea_manager.py list'

View File

@@ -70,12 +70,14 @@ async def get_current_user(
# We need to load the person relationship to avoid lazy loading issues
# Also load Person.address for nested profile response
from app.models.identity import Person
from app.models.identity.address import Address
from app.models.identity.address import Address, GeoPostalCode
stmt = (
select(User)
.where(User.id == int(user_id))
.options(
joinedload(User.person).joinedload(Person.address)
joinedload(User.person)
.joinedload(Person.address)
.joinedload(Address.postal_code) # <-- EZ KELL IDE: postal_code eager loading a zip/city property-khez
)
)
result = await db.execute(stmt)

View File

@@ -242,6 +242,9 @@ async def update_my_person(
street_name=update_dict.get("address_street_name"),
street_type=update_dict.get("address_street_type"),
house_number=update_dict.get("address_house_number"),
stairwell=update_dict.get("address_stairwell"),
floor=update_dict.get("address_floor"),
door=update_dict.get("address_door"),
parcel_id=update_dict.get("address_hrsz"),
)
if addr_id:

View File

@@ -57,12 +57,21 @@ class Address(Base):
# Robot és térképes funkciók számára
latitude: Mapped[Optional[float]] = mapped_column(Float)
longitude: Mapped[Optional[float]] = mapped_column(Float)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
# Kapcsolat az irányítószám táblához (zip/city lekéréshez)
postal_code: Mapped[Optional["GeoPostalCode"]] = relationship("GeoPostalCode", lazy="joined")
# ── Property-k a Pydantic AddressResponse zip/city mezőihez ──────────
@property
def zip(self) -> Optional[str]:
"""Visszaadja az irányítószám stringet a kapcsolódó GeoPostalCode rekordból."""
return self.postal_code.zip_code if self.postal_code else None
@property
def city(self) -> Optional[str]:
"""Visszaadja a város stringet a kapcsolódó GeoPostalCode rekordból."""
return self.postal_code.city if self.postal_code else None
class Rating(Base):

View File

@@ -133,7 +133,7 @@ class GeoService:
# Hozzáadjuk az atomizált kiegészítőket, ha vannak
if stairwell: full_text += f" {stairwell}. lph."
if floor: full_text += f" {floor}. em."
if door: full_text += f" {door}. ajtó"
if door: full_text += f" {door}. a."
# 6. Központi Address rekord rögzítése vagy lekérése (SELECT, majd INSERT)
stmt_addr = select(Address).where(

View File

@@ -0,0 +1,185 @@
# Test Scripts Inventory & Cleanup Audit
**Date:** 2026-06-05
**Author:** Roo (Fast Coder)
**Scope:** Root (`/opt/docker/dev/service_finder/`) and `backend/` directories
---
## Summary
This document provides a complete inventory of all test scripts, diagnostic scripts, patch scripts, and shell scripts found in the project root and `backend/` directories. Each script has been evaluated for usefulness and categorized for archival or retention.
---
## Root Directory Scripts
| # | File | Purpose | Verdict |
|---|------|---------|---------|
| 1 | `test_mvp.sh` | Bash-based MVP test: login, vehicle creation, org switch via curl | **Archive** — superseded by Python test scripts |
| 2 | `test_mvp.py` | Python MVP test: same flow as above via `requests` | **Archive** — one-time MVP validation |
| 3 | `test_catalog_filter.py` | Tests catalog filtering by `vehicle_class` via `AssetService` | **Active** — useful for catalog verification |
| 4 | `test_catalog_only.py` | Simpler catalog filter test via `AssetService` | **Archive** — duplicate of `test_catalog_filter.py` |
| 5 | `test_integration.py` | Integration test: login, catalog, vehicle registration via `TestClient` | **Active** — useful API smoke test |
| 6 | `test_onboard_fix.py` | Tests org onboard endpoint with `aiohttp` | **Archive** — one-time fix verification |
| 7 | `test_onboard_fix_v2.py` | v2 of onboard test with form-data login | **Archive** — superseded by v2, one-time use |
| 8 | `test_onboard_integration.py` | Integration test for onboard fix via `TestClient` | **Archive** — one-time fix verification |
| 9 | `sf_gitea.sh` | Gitea manager wrapper script | **Active** — useful utility |
| 10 | `sf_run.sh` | Docker exec wrapper for `sf_api` | **Active** — useful utility |
| 11 | `patch_auth_login.py` | Patches auth login endpoint with remember_me | **Archive** — one-time patch script |
| 12 | `patch_auth_service.py` | Patches auth service with shadow identity logic | **Archive** — one-time patch script |
| 13 | `patch_auth_service_fix.py` | Reverts shadow identity patch | **Archive** — one-time patch script |
| 14 | `patch_test.py` | Patches test_invitation_e2e.py with UUIDs | **Archive** — one-time patch script |
---
## Backend Directory Scripts (`backend/`)
### Test Scripts (test_*.py)
| # | File | Purpose | Verdict |
|---|------|---------|---------|
| 15 | `test_asset_e2e.py` | E2E asset test: creates users, org, vehicles via API | **Active** — useful E2E test |
| 16 | `test_asset_e2e_direct.py` | Direct DB + API asset test | **Archive** — duplicate of `test_asset_e2e.py` |
| 17 | `test_asset_e2e_fetch.py` | Asset fetch test with existing org members | **Archive** — variant of E2E test |
| 18 | `test_auth_e2e.py` | Auth E2E: registration, verification, login flow | **Active** — useful auth test |
| 19 | `test_auth_e2e_setup.py` | Sets up system parameters for auth tests | **Active** — useful setup script |
| 20 | `test_catalog_only.py` | Catalog filter test (duplicate of root version) | **Archive** — duplicate |
| 21 | `test_check_response.py` | Checks org switch response format | **Archive** — one-time debug |
| 22 | `test_complete_flow.py` | Comprehensive org switching + token refresh test | **Active** — useful flow test |
| 23 | `test_debug_switch.py` | Debug org switch error | **Archive** — one-time debug |
| 24 | `test_decode_token.py` | Decodes JWT to check scope_id | **Archive** — one-time debug |
| 25 | `test_email.py` | Tests email sending via configured provider | **Active** — useful email test |
| 26 | `test_final_verification.py` | Final verification of org switching flow | **Archive** — one-time verification |
| 27 | `test_flow_simple.sh` | Bash-based org switching flow test | **Archive** — superseded by Python tests |
| 28 | `test_i18n_implementation.py` | Tests i18n context management and middleware | **Active** — useful i18n test |
| 29 | `test_invitation_e2e.py` | E2E test for org invitation flow | **Active** — useful invitation test |
| 30 | `test_makes_filter.py` | Tests catalog makes filter via API | **Archive** — duplicate of catalog tests |
| 31 | `test_minimal_verification.py` | Minimal token refresh test | **Archive** — one-time verification |
| 32 | `test_schema_changes.py` | Checks DB schema for issue #179 columns | **Active** — useful schema audit |
| 33 | `test_token_debug.py` | Debug PATCH endpoint response | **Archive** — one-time debug |
| 34 | `test_token_refresh.py` | Tests token refresh on org switch | **Archive** — superseded by `test_complete_flow.py` |
| 35 | `test_token_refresh_simple.py` | Simpler token refresh test | **Archive** — superseded |
| 36 | `test_user55_api.py` | API test for User 55 endpoints | **Archive** — one-time user-specific test |
### Diagnostic Scripts (check_*.py)
| # | File | Purpose | Verdict |
|---|------|---------|---------|
| 37 | `check_asset_888.py` | Checks branch_id/catalog_id for TEST-888 asset | **Archive** — one-time debug |
| 38 | `check_branch.py` | Lists first 5 branches | **Archive** — one-time debug |
| 39 | `check_db.py` | Lists first 5 users and orgs | **Archive** — one-time debug |
| 40 | `check_user_scope.py` | Checks User 28 scope details | **Archive** — one-time debug |
| 41 | `check_vmd.py` | Checks Ford Focus VMD entries | **Archive** — one-time debug |
### Setup/Utility Scripts
| # | File | Purpose | Verdict |
|---|------|---------|---------|
| 42 | `complete_kyc_script.py` | Completes KYC for User 55 | **Archive** — one-time setup |
| 43 | `create_business_org.py` | Creates business org for User 55 | **Archive** — one-time setup |
| 44 | `create_business_via_api.py` | Creates business org via API for User 55 | **Archive** — one-time setup |
| 45 | `create_integration_session.py` | Creates integration_session.json | **Active** — useful for test setup |
| 46 | `create_vehicle_for_org.py` | Creates vehicle for Org 34 | **Archive** — one-time setup |
| 47 | `create_vehicle_via_api.py` | Creates vehicle via external API | **Archive** — one-time setup |
| 48 | `full_discovery_bot.py` | Seeds 200+ brands/models into DB | **Archive** — one-time seed |
| 49 | `get_token_for_user_29.py` | Generates JWT for User 29 | **Archive** — one-time debug |
| 50 | `manual_verification.py` | Manually verifies User 55 email | **Archive** — one-time fix |
| 51 | `raw_data_dump.py` | Dumps branches and assets from DB | **Active** — useful for data inspection |
| 52 | `trigger_auth_emails.py` | Triggers auth emails for debugging | **Active** — useful email debug |
| 53 | `verify_org_structure.py` | Verifies org structure for User 55 | **Archive** — one-time verification |
---
## Backend `tests/` Directory (`backend/tests/`)
| # | File | Purpose | Verdict |
|---|------|---------|---------|
| 54 | `e2e_smoke_test.py` | E2E smoke test for registration flow | **Active** — useful smoke test |
| 55 | `test_01_registration.py` | Ground Zero: registration endpoint test | **Active** — useful registration test |
| 56 | `test_04_business_onboarding.py` | Ground Zero: business onboarding test | **Active** — useful onboarding test |
| 57 | `test_credentials.json` | Test credentials data | **Active** — test data |
---
## Existing `tests/` Directory (`tests/`)
| # | File | Purpose | Verdict |
|---|------|---------|---------|
| 58 | `fire_drill_email.py` | Live email delivery verification via SendGrid | **Active** — useful email test |
| 59 | `sendgrid_live_test.py` | Direct SendGrid API test | **Active** — useful email test |
| 60 | `verify_auth_loop.py` | Auth endpoint simulation test | **Active** — useful auth test |
---
## Cleanup Plan
### Move to `tests/active/` (Keep — Useful for ongoing testing)
1. `test_catalog_filter.py` — catalog filtering verification
2. `test_integration.py` — API smoke test
3. `sf_gitea.sh` — Gitea manager wrapper
4. `sf_run.sh` — Docker exec wrapper
5. `backend/test_asset_e2e.py` — E2E asset test
6. `backend/test_auth_e2e.py` — Auth E2E test
7. `backend/test_auth_e2e_setup.py` — Auth setup
8. `backend/test_complete_flow.py` — Org switching flow
9. `backend/test_email.py` — Email test
10. `backend/test_i18n_implementation.py` — i18n test
11. `backend/test_invitation_e2e.py` — Invitation E2E
12. `backend/test_schema_changes.py` — Schema audit
13. `backend/create_integration_session.py` — Test setup
14. `backend/raw_data_dump.py` — Data inspection
15. `backend/trigger_auth_emails.py` — Email debug
16. `backend/tests/e2e_smoke_test.py` — Smoke test
17. `backend/tests/test_01_registration.py` — Registration test
18. `backend/tests/test_04_business_onboarding.py` — Onboarding test
19. `backend/tests/test_credentials.json` — Test data
### Move to `tests/archive/` with `.old` extension (Obsolete / One-time use)
1. `test_mvp.sh``tests/archive/test_mvp.sh.old`
2. `test_mvp.py``tests/archive/test_mvp.py.old`
3. `test_catalog_only.py``tests/archive/test_catalog_only.py.old`
4. `test_onboard_fix.py``tests/archive/test_onboard_fix.py.old`
5. `test_onboard_fix_v2.py``tests/archive/test_onboard_fix_v2.py.old`
6. `test_onboard_integration.py``tests/archive/test_onboard_integration.py.old`
7. `patch_auth_login.py``tests/archive/patch_auth_login.py.old`
8. `patch_auth_service.py``tests/archive/patch_auth_service.py.old`
9. `patch_auth_service_fix.py``tests/archive/patch_auth_service_fix.py.old`
10. `patch_test.py``tests/archive/patch_test.py.old`
11. `backend/test_asset_e2e_direct.py``tests/archive/test_asset_e2e_direct.py.old`
12. `backend/test_asset_e2e_fetch.py``tests/archive/test_asset_e2e_fetch.py.old`
13. `backend/test_catalog_only.py``tests/archive/test_catalog_only.py.old`
14. `backend/test_check_response.py``tests/archive/test_check_response.py.old`
15. `backend/test_debug_switch.py``tests/archive/test_debug_switch.py.old`
16. `backend/test_decode_token.py``tests/archive/test_decode_token.py.old`
17. `backend/test_final_verification.py``tests/archive/test_final_verification.py.old`
18. `backend/test_flow_simple.sh``tests/archive/test_flow_simple.sh.old`
19. `backend/test_makes_filter.py``tests/archive/test_makes_filter.py.old`
20. `backend/test_minimal_verification.py``tests/archive/test_minimal_verification.py.old`
21. `backend/test_token_debug.py``tests/archive/test_token_debug.py.old`
22. `backend/test_token_refresh.py``tests/archive/test_token_refresh.py.old`
23. `backend/test_token_refresh_simple.py``tests/archive/test_token_refresh_simple.py.old`
24. `backend/test_user55_api.py``tests/archive/test_user55_api.py.old`
25. `backend/check_asset_888.py``tests/archive/check_asset_888.py.old`
26. `backend/check_branch.py``tests/archive/check_branch.py.old`
27. `backend/check_db.py``tests/archive/check_db.py.old`
28. `backend/check_user_scope.py``tests/archive/check_user_scope.py.old`
29. `backend/check_vmd.py``tests/archive/check_vmd.py.old`
30. `backend/complete_kyc_script.py``tests/archive/complete_kyc_script.py.old`
31. `backend/create_business_org.py``tests/archive/create_business_org.py.old`
32. `backend/create_business_via_api.py``tests/archive/create_business_via_api.py.old`
33. `backend/create_vehicle_for_org.py``tests/archive/create_vehicle_for_org.py.old`
34. `backend/create_vehicle_via_api.py``tests/archive/create_vehicle_via_api.py.old`
35. `backend/full_discovery_bot.py``tests/archive/full_discovery_bot.py.old`
36. `backend/get_token_for_user_29.py``tests/archive/get_token_for_user_29.py.old`
37. `backend/manual_verification.py``tests/archive/manual_verification.py.old`
38. `backend/verify_org_structure.py``tests/archive/verify_org_structure.py.old`
---
## Statistics
- **Total scripts found:** 60
- **Active (keep in place or move to `tests/active/`):** 19
- **Archive (move to `tests/archive/` with `.old`):** 38
- **Already in `tests/` (keep in place):** 3

View File

@@ -13,6 +13,22 @@
<!-- Content -->
<div class="relative z-10 mx-auto max-w-4xl px-4 py-8 sm:px-6 lg:px-8">
<!-- Loading Spinner -->
<div
v-if="isPageLoading"
class="flex flex-col items-center justify-center py-24"
>
<div class="flex h-16 w-16 items-center justify-center rounded-full bg-[#00E5A0]/10 mb-4">
<svg class="h-8 w-8 animate-spin text-[#00E5A0]" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
</div>
<p class="text-white/60 text-sm">Profil adatok betöltése...</p>
</div>
<!-- Profile Content (shown when not loading) -->
<template v-if="!isPageLoading">
<!-- Account Info Card -->
<div
class="relative mb-8 rounded-2xl border border-white/10 bg-black/40 p-6 backdrop-blur-xl shadow-xl shadow-black/20"
@@ -386,10 +402,14 @@
</div>
<div v-else class="text-sm space-y-1">
<p class="text-white font-medium">{{ address?.zip || '—' }} {{ address?.city || '' }}</p>
<p class="text-white font-medium">{{ address?.street_name || '' }} {{ address?.street_type || '' }} {{ address?.house_number || '' }}</p>
<p v-if="address?.stairwell || address?.floor || address?.door" class="text-white font-medium">
Lépcsőház: {{ address?.stairwell || '' }}, Emelet: {{ address?.floor || '' }}, Ajtó: {{ address?.door || '' }}
<p class="text-white font-medium">
<template v-if="authStore.user?.person?.address?.zip || authStore.user?.person?.address?.city">
{{ authStore.user?.person?.address?.zip }} {{ authStore.user?.person?.address?.city }},
</template>
{{ authStore.user?.person?.address?.street_name }} {{ authStore.user?.person?.address?.street_type }} {{ authStore.user?.person?.address?.house_number }}
</p>
<p v-if="authStore.user?.person?.address?.stairwell || authStore.user?.person?.address?.floor || authStore.user?.person?.address?.door" class="text-white font-medium">
Lépcsőház: {{ authStore.user?.person?.address?.stairwell || '' }}, Emelet: {{ authStore.user?.person?.address?.floor || '' }}, Ajtó: {{ authStore.user?.person?.address?.door || '' }}
</p>
</div>
</div>
@@ -404,6 +424,14 @@
</div>
</div>
<!-- 🩻 DIAGNOSTIC JSON DUMP -->
<div class="rounded-2xl border border-white/10 bg-black/40 p-4 mt-6 backdrop-blur-xl shadow-xl shadow-black/20">
<details>
<summary class="text-xs font-semibold text-[#00E5A0] cursor-pointer select-none mb-2">🩻 RÖNTGEN: authStore.user?.person?.address</summary>
<pre class="text-xs text-green-400 bg-black p-4 mt-2 overflow-auto rounded-lg max-h-64">{{ JSON.stringify(authStore.user?.person?.address, null, 2) }}</pre>
</details>
</div>
<!-- Back to Garage Button -->
<div class="mt-8 text-center">
<button
@@ -426,6 +454,7 @@
Vissza a Garázsba
</button>
</div>
</template>
</div>
<!-- Change Password Modal -->
@@ -530,7 +559,7 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import { ref, reactive, computed, watch, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useAuthStore } from '../stores/auth'
@@ -541,12 +570,26 @@ const router = useRouter()
const { t } = useI18n()
const authStore = useAuthStore()
// ── Page-level loading state ──────────────────────────────────────────
const isPageLoading = ref(true)
// ── Computed helpers ──────────────────────────────────────────────────
const person = computed(() => authStore.user?.person ?? null)
const address = computed(() => person.value?.address ?? null)
const identityDocs = computed(() => person.value?.identity_docs ?? null)
const isCityLoading = ref(false)
// ── Force-fetch fresh profile data on mount ───────────────────────────
onMounted(async () => {
try {
await authStore.fetchUser()
} catch (err) {
console.warn('ProfileView: fetchUser failed', err)
} finally {
isPageLoading.value = false
}
})
// ── Edit State ────────────────────────────────────────────────────────
const isEditing = ref(false)
const isSaving = ref(false)
@@ -646,7 +689,7 @@ watch(
zipTimeout = setTimeout(async () => {
try {
const countryCode = 'HU' // Default to HU for now
const response = await api.get('/system/zip-lookup', {
const response = await api.get('/system/parameters/zip-lookup', {
params: { country_code: countryCode, zip_code: newZip }
})
if (response.data?.city) {

View File

@@ -1,58 +0,0 @@
#!/usr/bin/env python3
"""
Test catalog filtering directly via AssetService.
"""
import asyncio
import sys
sys.path.insert(0, '/app')
from app.db.session import async_sessionmaker
from app.services.asset_service import AssetService
async def test():
async_session = async_sessionmaker()
async with async_session() as session:
async with session.begin():
# Get makes
makes = await AssetService.get_makes(session)
print(f"Total makes: {len(makes)}")
if not makes:
print("No makes in database")
return
test_make = makes[0]
print(f"Testing with make: {test_make}")
# Get all models
models_all = await AssetService.get_models(session, test_make)
print(f"All models for {test_make}: {len(models_all)}")
# Get filtered by passenger_car
models_car = await AssetService.get_models(session, test_make, 'passenger_car')
print(f"Models filtered by 'passenger_car': {len(models_car)}")
# Get filtered by motorcycle
models_moto = await AssetService.get_models(session, test_make, 'motorcycle')
print(f"Models filtered by 'motorcycle': {len(models_moto)}")
# Verify filtering works
if models_car and len(models_car) <= len(models_all):
print("✅ PASS: Car filter returns subset or equal")
else:
print("⚠ WARNING: Car filter anomaly")
# Check if there's any difference
if models_car != models_all:
print("✅ PASS: Filtering actually changes results")
else:
print("⚠ WARNING: Filtering returns same results (maybe no vehicle_class data)")
# Print a few examples
if models_all:
print(f"Sample models: {models_all[:3]}")
if models_car:
print(f"Sample car models: {models_car[:3]}")
if models_moto:
print(f"Sample motorcycle models: {models_moto[:3]}")
if __name__ == "__main__":
asyncio.run(test())

View File

@@ -0,0 +1,257 @@
#!/usr/bin/env python3
"""
Playwright E2E Test: Profile Address Persistence (Stairwell / Floor / Door)
This test:
1. Logs in as the configured test user (via LoginModal on the landing page).
2. Navigates to the Profile page (/profile).
3. Enters edit mode, fills every address field (city, zip, street, house number,
stairwell, floor, door), and saves.
4. Reloads the page and verifies that the stairwell, floor, and door values
persisted after the page refresh.
Requirements:
- playwright (Python) + dotenv
- .env.test file at the project root with TEST_USER_EMAIL and TEST_USER_PASS
Usage:
python tests/active/e2e_profile_address.py
To see the browser visually, change HEADLESS=False below.
"""
import os
import sys
import re
from pathlib import Path
from dotenv import load_dotenv
from playwright.sync_api import sync_playwright, TimeoutError as PwTimeout
# ── Configuration ──────────────────────────────────────────────────────────
BASE_URL = "https://app.servicefinder.hu"
HEADLESS = True # Set to False for visual debugging
SLOW_MO = 0 # ms delay between actions (useful for debugging)
ACTION_TIMEOUT = 15_000 # ms default timeout for element lookups
# ── Load .env.test from project root ──────────────────────────────────────
ENV_PATH = Path(__file__).resolve().parents[1] / ".env.test"
if not ENV_PATH.exists():
raise FileNotFoundError(
f".env.test not found at {ENV_PATH}. "
"Create it with TEST_USER_EMAIL and TEST_USER_PASS."
)
load_dotenv(dotenv_path=ENV_PATH, override=True)
TEST_EMAIL = os.getenv("TEST_USER_EMAIL")
TEST_PASS = os.getenv("TEST_USER_PASS")
if not TEST_EMAIL or not TEST_PASS:
raise ValueError(
"TEST_USER_EMAIL and/or TEST_USER_PASS are missing from .env.test. "
f"Loaded from: {ENV_PATH}"
)
# ── Test data ──────────────────────────────────────────────────────────────
ADDRESS_DATA = {
"address_city": "Dunakeszi",
"address_zip": "2120",
"address_street_name": "Határ",
"address_street_type": "utca",
"address_house_number": "8",
"address_stairwell": "A",
"address_floor": "3",
"address_door": "5",
}
# ═══════════════════════════════════════════════════════════════════════════
# Helper functions
# ═══════════════════════════════════════════════════════════════════════════
def log(msg: str) -> None:
"""Simple stdout logger."""
print(f"[E2E] {msg}")
def fill_input(page, placeholder: str, value: str) -> None:
"""Locate an <input> by its placeholder and fill it."""
locator = page.locator(f'input[placeholder="{placeholder}"]')
locator.wait_for(state="visible", timeout=ACTION_TIMEOUT)
locator.fill(value)
log(f" Filled '{placeholder}''{value}'")
# ═══════════════════════════════════════════════════════════════════════════
# Main test flow
# ═══════════════════════════════════════════════════════════════════════════
def main() -> None:
with sync_playwright() as pw:
# ── 1. Launch browser (local or remote via BROWSERLESS_URL) ──
browserless_url = os.getenv("BROWSERLESS_URL")
if browserless_url:
# Connect to a remote Browserless / Chrome container
browser = pw.chromium.connect_over_cdp(browserless_url)
log(f"Connected to remote browser at {browserless_url}")
else:
# Local fallback
browser = pw.chromium.launch(
headless=os.getenv("HEADLESS", "True").lower() == "true",
slow_mo=SLOW_MO,
)
log(f"Browser launched locally (headless={os.getenv('HEADLESS', 'True')})")
context = browser.new_context(
viewport={"width": 1280, "height": 800},
locale="hu-HU",
)
page = context.new_page()
log(f"Browser launched (headless={HEADLESS})")
# ── 2. Open landing page & login ─────────────────────────────
log("Navigating to landing page …")
page.goto(BASE_URL, wait_until="networkidle", timeout=30_000)
# Click the "Garázs Nyitása" button to open the LoginModal
login_trigger = page.locator("button:has-text('Garázs Nyitása')")
login_trigger.wait_for(state="visible", timeout=ACTION_TIMEOUT)
login_trigger.click()
log("Login modal opened")
# Fill email & password
page.fill("#login-email", TEST_EMAIL)
page.fill("#login-password", TEST_PASS)
log("Login credentials entered")
# Click the submit button (the first .btn-premium inside the visible form)
submit_btn = page.locator("form button[type='submit']").first
submit_btn.wait_for(state="visible", timeout=ACTION_TIMEOUT)
submit_btn.click()
log("Login submitted waiting for navigation …")
# Wait for redirect away from landing page (to /dashboard or /complete-kyc)
try:
page.wait_for_url(
lambda url: "/dashboard" in url or "/complete-kyc" in url,
timeout=15_000,
)
except PwTimeout:
# If still on landing, maybe login failed dump state
log(f"URL after login attempt: {page.url}")
page.screenshot(path="/tmp/e2e_login_fail.png")
raise AssertionError(
"Login failed did not redirect to dashboard. "
"Check credentials or UI state."
)
log(f"Login successful current URL: {page.url}")
# ── 3. Navigate to Profile page ──────────────────────────────
log("Navigating to /profile …")
page.goto(f"{BASE_URL}/profile", wait_until="networkidle", timeout=20_000)
# Wait for the profile page to finish loading (spinner disappears)
page.wait_for_selector("text=Profil beállítások", timeout=ACTION_TIMEOUT)
# Also wait until the loading spinner is gone
page.wait_for_function(
"() => !document.querySelector('.animate-spin')",
timeout=15_000,
)
log("Profile page loaded")
# ── 4. Enter edit mode ───────────────────────────────────────
edit_btn = page.locator("button:has-text('Szerkesztés')")
edit_btn.wait_for(state="visible", timeout=ACTION_TIMEOUT)
edit_btn.click()
log("Edit mode activated")
# ── 5. Fill in all address fields ────────────────────────────
log("Filling address form …")
fill_input(page, "Irányítószám", ADDRESS_DATA["address_zip"])
fill_input(page, "Város", ADDRESS_DATA["address_city"])
fill_input(page, "Utca", ADDRESS_DATA["address_street_name"])
fill_input(page, "Közterület jellege", ADDRESS_DATA["address_street_type"])
fill_input(page, "Házszám", ADDRESS_DATA["address_house_number"])
fill_input(page, "Lépcsőház", ADDRESS_DATA["address_stairwell"])
fill_input(page, "Emelet", ADDRESS_DATA["address_floor"])
fill_input(page, "Ajtó", ADDRESS_DATA["address_door"])
# ── 6. Save ──────────────────────────────────────────────────
save_btn = page.locator("button:has-text('Mentés')")
save_btn.wait_for(state="visible", timeout=ACTION_TIMEOUT)
save_btn.click()
log("Save clicked waiting for confirmation …")
# Wait for the success message to appear
success_locator = page.locator("text=A profil adatok és okmányok sikeresen frissítve!")
try:
success_locator.wait_for(state="visible", timeout=10_000)
log("Save succeeded success message visible")
except PwTimeout:
# Check for error message
error_locator = page.locator(".text-red-400")
if error_locator.is_visible():
error_text = error_locator.text_content()
log(f"Save FAILED with error: {error_text}")
page.screenshot(path="/tmp/e2e_save_fail.png")
raise AssertionError(f"Save failed: {error_text}")
page.screenshot(path="/tmp/e2e_save_timeout.png")
raise AssertionError(
"Save timeout neither success nor error message appeared."
)
# ── 7. Reload and verify persistence ─────────────────────────
log("Reloading page to verify persistence …")
page.reload(wait_until="networkidle")
page.wait_for_selector("text=Profil beállítások", timeout=ACTION_TIMEOUT)
page.wait_for_function(
"() => !document.querySelector('.animate-spin')",
timeout=15_000,
)
log("Page reloaded")
# Read the display text in the read-only address section
# The read-only view shows: Lépcsőház: …, Emelet: …, Ajtó: …
page_body = page.locator("body").inner_text()
log("── Read-only address block ──")
# Use a regex to extract values
stairwell_match = re.search(r"Lépcsőház:\s*(\S+)", page_body)
floor_match = re.search(r"Emelet:\s*(\S+)", page_body)
door_match = re.search(r"Ajtó:\s*(\S+)", page_body)
stairwell_val = stairwell_match.group(1) if stairwell_match else ""
floor_val = floor_match.group(1) if floor_match else ""
door_val = door_match.group(1) if door_match else ""
log(f" Stairwell (Lépcsőház): '{stairwell_val}'")
log(f" Floor (Emelet): '{floor_val}'")
log(f" Door (Ajtó): '{door_val}'")
# ── 8. Assertion: all three values must be present ───────────
errors = []
if not stairwell_val or stairwell_val in ("", "null", "None"):
errors.append(f"stairwell (expected '{ADDRESS_DATA['address_stairwell']}', got '{stairwell_val}')")
if not floor_val or floor_val in ("", "null", "None"):
errors.append(f"floor (expected '{ADDRESS_DATA['address_floor']}', got '{floor_val}')")
if not door_val or door_val in ("", "null", "None"):
errors.append(f"door (expected '{ADDRESS_DATA['address_door']}', got '{door_val}')")
if errors:
page.screenshot(path="/tmp/e2e_persistence_fail.png")
raise AssertionError(
"Hibás backend mentés! Az emelet/ajtó adatok elvesztek az oldal "
f"frissítése után.\n Missing/empty fields: {', '.join(errors)}"
)
log("Sikeres E2E teszt: Az adatok megmaradtak a backend frissítés után is!")
log("ALL CHECKS PASSED.\n")
browser.close()
if __name__ == "__main__":
main()