# CODE_AUDIT

## Table of Contents
- Repo Inventory
- Packages and Versions
- Routes Matrix
- Middleware Map
- FormRequests Matrix
- Controller → Service Wiring
- Database Catalog (Tables, Keys, Indexes, FKs)
- Mermaid ERD (snapshot)
- Config Summary
- Schedulers and Queues
- Notable Findings

## Repo Inventory
- app/
  - Http/Controllers: AIController, PaymentsController, SavedAdsController, Support/TicketsController, Admin/{TicketsController,OpportunitiesController,FeaturedAdsController,ScrapeProfilesController}, TelegramWebhookController
  - Http/Middleware: RateLimitPerUser, FeatureGuard, SimulateProtect
  - Http/Requests: AiRespondRequest, PaymentCreateRequest, PaymentVerifyRequest, SavedAdStoreRequest, TicketStoreRequest, AdminTicketReplyRequest, SimulateClickRequest
  - Services: AI/{LlmProvider,OpenAiProvider}, Ads/{Normalizer,CityMedianService,OpportunityScorer,MajidApiClient,Deduper}, Growth/{EntitlementResolver,QuotaService}, Payments/PaymentService, Messaging/TelegramBot, Analytics/EventBus, Content/VectorStoreService, Experiments/ExperimentService
  - Jobs: PushSchedulerJob, PushOpportunitiesJob, PushFeaturedAdsJob, ScoreOpportunitiesJob, BamaPullLatestJob, BamaSearchJob, BamaDetailsJob, DivarScrapeJob, GrowthDailyJob, PaymentsReconcileJob, GenerateCityReportJob
  - Filament/Resources and Pages: placeholders for admin UI
  - Http/Kernel.php
- routes/: api.php, telegraph.php
- config/: ai.php, ads.php, payments.php, feature_flags.php, quotas.php, telegraph.php, growth.php
- database/: migrations/* (19 files), seeders/* (9 files)
- docs/: PRD.md, Product design.md, ERD.mmd, IMPLEMENTATION_PLAN.md, ARCHITECTURE.md, REQUIREMENTS_CHECKLIST.md, QUALITY_GATES.md, RISK_REGISTER.md, TEST_PLAN.md, API_SPEC.internal.md
- tests/: Feature/* (QuotaService, TrialsLedger, ReferralCap, SimulateClickProtection)

## Packages and Versions
- Composer files are missing; cannot assert versions from code. Per docs (docs/ARCHITECTURE.md): Laravel 10, PHP 8.2; packages: defstudio/telegraph (Telegram), filament/filament (admin), spatie/laravel-permission (RBAC), shetabit/payment (Zarinpal), barryvdh/laravel-dompdf (optional PDFs).
- .env/.env.example not present; config relies on env keys noted below.

## Routes Matrix

- POST `/api/ai/respond` → AIController@respond
  - Middleware: `rate.user`, `feature.guard:pricecheck` (routes/api.php:14–16)

- POST `/api/payments/create` → PaymentsController@create (routes/api.php:19)
  - Middleware: none

- GET `/api/payments/verify` → PaymentsController@verify (routes/api.php:20)
  - Middleware: none

- POST `/api/payments/reconcile` → PaymentsController@reconcile (routes/api.php:21)
  - Middleware: none

- POST `/api/user/saved_ads` → SavedAdsController@store (routes/api.php:24)
  - Middleware: none

- DELETE `/api/user/saved_ads/{id}` → SavedAdsController@destroy (routes/api.php:25)
  - Middleware: none

- GET `/api/user/saved_ads` → SavedAdsController@index (routes/api.php:26)
  - Middleware: `feature.guard:deal_view` (note mismatch with FeatureGuard keys)

- POST `/api/support/tickets` → Support\TicketsController@store (routes/api.php:29)
  - Middleware: none

- GET `/api/support/tickets` → Support\TicketsController@index (routes/api.php:30)
  - Middleware: none

- POST `/api/admin/tickets/{id}/reply` → Admin\TicketsController@reply (routes/api.php:33)
  - Middleware: none (should be admin-only)

- POST `/api/admin/opportunities/push` → Admin\OpportunitiesController@push (routes/api.php:34)
  - Middleware: `feature.guard:instant_push`

- POST `/api/admin/featured_ads/push` → Admin\FeaturedAdsController@push (routes/api.php:35)
  - Middleware: `feature.guard:instant_push`

- POST `/api/simulate/click` → SimulateController@click (routes/api.php:36)
  - Middleware: `simulate.protect`

- POST `/telegram/webhook` → TelegramWebhookController@handle (routes/telegraph.php:6)
  - Middleware: none (should validate secret/signature)

AI Rate Limit: Implemented at 5 req/min per user/IP (app/Http/Middleware/RateLimitPerUser.php:20; routes/api.php:14).

Note on route file loading: `routes/telegraph.php` is not auto-loaded by Laravel. Ensure it is required (e.g., from `routes/api.php`) or registered in `RouteServiceProvider`, otherwise `/telegram/webhook` will 404 even if the file exists.

## Middleware Map
- `rate.user` → RateLimitPerUser (app/Http/Middleware/RateLimitPerUser.php)
- `feature.guard` → FeatureGuard (app/Http/Middleware/FeatureGuard.php)
- `simulate.protect` → SimulateProtect (app/Http/Middleware/SimulateProtect.php)

## FormRequests Matrix
- `/ai/respond` → AiRespondRequest (app/Http/Requests/AiRespondRequest.php:10–25)
- `/payments/create` → PaymentCreateRequest (app/Http/Requests/PaymentCreateRequest.php:10–18)
- `/payments/verify` → PaymentVerifyRequest (app/Http/Requests/PaymentVerifyRequest.php:10–18)
- `/user/saved_ads` → SavedAdStoreRequest (app/Http/Requests/SavedAdStoreRequest.php:12–41)
- `/support/tickets` → TicketStoreRequest (app/Http/Requests/TicketStoreRequest.php:10–18)
- `/admin/tickets/{id}/reply` → AdminTicketReplyRequest (app/Http/Requests/AdminTicketReplyRequest.php:10–16)
- `/simulate/click` → SimulateClickRequest (app/Http/Requests/SimulateClickRequest.php:10–16)

## Controller → Service Wiring
- AIController → LlmProvider (app/Services/AI/OpenAiProvider.php) with structured JSON retry (app/Http/Controllers/AIController.php:20–31,42–51).
- PaymentsController → direct DB; PaymentService stub exists (app/Services/Payments/PaymentService.php) but unused.
- SimulateController → direct DB update to push_logs (clicked_cta_at) (app/Http/Controllers/SimulateController.php:12–18).
- FeatureGuard → QuotaService + EntitlementResolver (app/Services/Growth/*).
- EventBus present but not used (app/Services/Analytics/EventBus.php).
- Ads services stubs (Normalizer, Deduper, CityMedianService, OpportunityScorer), jobs scaffolds (app/Jobs/*) — not yet integrated.

## Database Catalog

Core
- cities: id, name (UK), province; used for normalization. FK refs from users, opportunities (database/migrations/2025_09_01_000001_create_cities_and_aliases_tables.php).
- city_aliases: id, city_id (FK→cities), alias (UK).
- users: id, tg_id (UK), city_id (FK→cities), role, plan, notif_level, timestamps (2025_09_01_000002_create_users_table.php).
- plans: id, key (UK), name, amount_irr, duration_days, is_active (2025_09_01_000003_create_plans_table.php).
- subscriptions: id, user_id (FK), plan_id (FK), plan (str), starts_at, ends_at, status default inactive (2025_09_01_000004_create_subscriptions_table.php).
- payments: id, user_id (FK), plan_id (FK, nullable), gateway, ref, amount, status, payload_json (2025_09_01_000005_create_payments_table.php).

Content & Config
- menu_items: key (IDX), title, emoji, enabled, ord, visible_roles JSON (2025_09_01_000006_create_menu_and_prompts_tables.php).
- prompt_configs: module (IDX), model, temperature, top_p, enable_file_search (2025_09_01_000006_create_menu_and_prompts_tables.php).
- vector_stores: name, openai_vector_store_id (UK), is_active (2025_09_01_000007_create_vector_store_tables.php).
- vector_files: vector_store_id (FK), openai_file_id (UK), tag, status (2025_09_01_000007_create_vector_store_tables.php).

Ads/Opportunities
- ads_raw: source, payload_json, fetched_at (IDX source+fetched_at) (2025_09_01_000008_create_ads_tables.php).
- opportunities: source, code, brand, model, trim, year, km, city_id (FK), price, body_status, link, contact_ref, dedup_key (UK), score, reasons_json, status, ts_posted, pushed_at (IDX city_id+brand+model+year) (2025_09_01_000008_create_ads_tables.php).
- featured_ads: title, description, price, city, link, target_roles, target_cities, scheduled_at, pushed_at, status (2025_09_01_000008_create_ads_tables.php).
- scrape_profiles: name, url, class_name, element_id, city, keywords, enabled (2025_09_01_000008_create_ads_tables.php).

Static Content
- legal_files: type, file_path (2025_09_01_000009_create_content_tables.php).
- checklists: name, type, questions_json (2025_09_01_000009_create_content_tables.php).
- city_reports: city, week_no, summary_json (2025_09_01_000009_create_content_tables.php).

Chat & Events
- chat_threads: user_id (FK), module, last_response_id, expires_at (IDX user_id+module; extra index added in 2025_09_01_000013_create_menu_indexes_and_more.php) (2025_09_01_000010_create_chat_and_events_tables.php).
- chats: user_id (FK), module, role, text, response_id, token_in, token_out, latency_ms (2025_09_01_000010_create_chat_and_events_tables.php).
- events: user_id (FK), type, payload_json (indexes added: events_type_ts, events_user_ts) (2025_09_01_000010_create_chat_and_events_tables.php; 2025_09_03_000001_harden_quotas_push_events_addons.php).

Saved Items and Support
- saved_ads: user_id (FK), source, opportunity_id (FK nullable), external_source, external_code, link; uniques: (user_id, opportunity_id) and (user_id, external_source, external_code) (2025_09_01_000011_create_saved_ads_table.php).
- tickets: user_id (FK), subject, body, status, admin_replied_at, admin_last_reply_text (2025_09_01_000012_create_tickets_table.php).

Growth (Phase 2A)
- plan_entitlements: plan_id (FK), key, int_value, json_value; unique(plan_id,key) (2025_09_02_000001_create_entitlements_and_quotas.php).
- user_quotas: user_id (FK), key, used, period_start, period_end; later added period (default month) and composite index uq_composite(user_id,period,key,period_start,period_end) (2025_09_02_000001..., 2025_09_03_000001...).
- addons: key (UK), name, entitlements_json, is_active (2025_09_02_000002_create_addons_tables.php).
- user_addons: user_id (FK), addon_id (FK), starts_at, ends_at, status; unique(user_id,addon_id); plus unique window(user_id,addon_id,starts_at,ends_at) (2025_09_02_000002..., 2025_09_03_000001...).
- wallet_ledger: user_id (FK), type, amount_irr, reason, meta_json (2025_09_02_000003_create_wallet_and_trials.php).
- trials: user_id (FK), status, started_at, ends_at, penalty_points (2025_09_02_000003_create_wallet_and_trials.php).
- experiments: key (UK), variant_keys, is_active (2025_09_02_000004_create_experiments_and_push.php).
- experiment_assignments: user_id (FK), experiment_id (FK), variant_key, assigned_at; unique(user_id,experiment_id) (2025_09_02_000004...).
- push_campaigns: key (UK), name, payload_json, schedule_json, status (2025_09_02_000004...).
- push_logs: user_id (FK), campaign_id (FK), sent_at, delivered_at, opened_at, clicked_cta_at, meta_json; indexes on (user_id,campaign_id) and sent_at (2025_09_02_000004..., 2025_09_03_000001...).
- referral_links: user_id (FK), code (UK) (2025_09_02_000005_create_referrals_and_cancellations.php).
- referral_events: link_id (FK), user_id (FK nullable), type, meta_json (2025_09_02_000005...).
- cancellations: user_id (FK), status, reason, selected_offer, meta_json (2025_09_02_000005...).

Seeders present for menu, prompts, checklists, plans, plan entitlements, experiments, cities/aliases (database/seeders/*).

## Mermaid ERD (snapshot)

```mermaid
erDiagram
  USERS ||--o{ SUBSCRIPTIONS : has
  USERS ||--o{ PAYMENTS : has
  USERS ||--o{ CHAT_THREADS : has
  USERS ||--o{ CHATS : has
  USERS ||--o{ EVENTS : has
  USERS ||--o{ SAVED_ADS : has
  USERS ||--o{ TICKETS : has
  USERS ||--o{ USER_ADDONS : has
  USERS ||--o{ WALLET_LEDGER : has
  USERS ||--o{ TRIALS : has
  USERS ||--o{ EXPERIMENT_ASSIGNMENTS : has
  USERS ||--o{ PUSH_LOGS : receives
  USERS ||--o{ REFERRAL_LINKS : owns
  USERS ||--o{ CANCELLATIONS : files

  PLANS ||--o{ PLAN_ENTITLEMENTS : defines
  ADDONS ||--o{ USER_ADDONS : grants
  EXPERIMENTS ||--o{ EXPERIMENT_ASSIGNMENTS : assigns
  PUSH_CAMPAIGNS ||--o{ PUSH_LOGS : records
  CITIES ||--o{ CITY_ALIASES : maps
  CITIES ||--o{ USERS : used_by
  CITIES ||--o{ OPPORTUNITIES : used_by
  VECTOR_STORES ||--o{ VECTOR_FILES : contains
  REFERRAL_LINKS ||--o{ REFERRAL_EVENTS : logs

  USERS { bigint id PK }
  SUBSCRIPTIONS { bigint id PK, bigint user_id FK, bigint plan_id FK, string status }
  PAYMENTS { bigint id PK, bigint user_id FK, bigint plan_id FK, string status }
  PLAN_ENTITLEMENTS { bigint id PK, bigint plan_id FK, string key }
  USER_QUOTAS { bigint id PK, bigint user_id FK, string key, string period, int used }
  ADDONS { bigint id PK, string key UK }
  USER_ADDONS { bigint id PK, bigint user_id FK, bigint addon_id FK }
  WALLET_LEDGER { bigint id PK, bigint user_id FK }
  TRIALS { bigint id PK, bigint user_id FK }
  EXPERIMENTS { bigint id PK, string key UK }
  EXPERIMENT_ASSIGNMENTS { bigint id PK, bigint user_id FK, bigint experiment_id FK }
  PUSH_CAMPAIGNS { bigint id PK, string key UK }
  PUSH_LOGS { bigint id PK, bigint user_id FK, bigint campaign_id FK }
  REFERRAL_LINKS { bigint id PK, bigint user_id FK, string code UK }
  REFERRAL_EVENTS { bigint id PK, bigint link_id FK, bigint user_id FK }
  CANCELLATIONS { bigint id PK, bigint user_id FK }
  OPPORTUNITIES { bigint id PK, bigint city_id FK, string dedup_key UK }
  VECTOR_STORES { bigint id PK, string openai_vector_store_id UK }
  VECTOR_FILES { bigint id PK, bigint vector_store_id FK, string openai_file_id UK }
  ADS_RAW { bigint id PK }
  MENU_ITEMS { bigint id PK }
  PROMPT_CONFIGS { bigint id PK }
  LEGAL_FILES { bigint id PK }
  CHECKLISTS { bigint id PK }
  CITY_REPORTS { bigint id PK }
  CHATS { bigint id PK, bigint user_id FK }
  CHAT_THREADS { bigint id PK, bigint user_id FK }
  EVENTS { bigint id PK, bigint user_id FK }
  SAVED_ADS { bigint id PK, bigint user_id FK }
  TICKETS { bigint id PK, bigint user_id FK }
```

## Config Summary
- `config/ai.php`: timeout 20s, retries 1; per-module model and file_search toggles (config/ai.php:3–15).
- `config/ads.php`: thresholds −15% primary, −10% fallback; TTL 72h; median_min_samples=20 (config/ads.php:3–7).
- `config/payments.php`: currency IRR; `ZARINPAL_MERCHANT_ID`, `ZARINPAL_CALLBACK_URL` envs (config/payments.php:3–6).
- `config/feature_flags.php`: booleans for modules (config/feature_flags.php:3–9).
- `config/quotas.php`: default quotas per-module (not directly used; quotas resolved via plan entitlements) (config/quotas.php:3–9).
- `config/telegraph.php`: bot token/secret/webhook (config/telegraph.php:3–10).
- `config/growth.php`: trial penalty, challenge reward, referral daily cap (config/growth.php:3–7).

## Schedulers and Queues
- Jobs implement ShouldQueue (e.g., app/Jobs/PushSchedulerJob.php). No Console Kernel present (app/Console/Kernel.php missing). Cron schedule definitions are absent — push scheduler, scoring, growth daily, payments reconcile not scheduled.
- Queue connection not defined (no composer/.env). Assuming database queue per docs.

## Notable Findings
- AI group rate limit enforced at 5/min per user/IP (app/Http/Middleware/RateLimitPerUser.php:20; routes/api.php:14).
- FeatureGuard wires quotas but route uses `feature.guard:deal_view` while guard expects `view_deals`; this will default quotas to valuations (routes/api.php:26 vs app/Http/Middleware/FeatureGuard.php:18,24).
- Payments verify updates the latest pending payment and activates/inserts subscription; lacks idempotency and account binding beyond user_id=1 stub (app/Http/Controllers/PaymentsController.php:16–24,31–54).
- Event tracking not implemented in controllers; EventBus exists (app/Services/Analytics/EventBus.php).
- TelegramWebhookController is a stub; no Telegraph secret validation.
- Admin endpoints lack explicit admin auth middleware.

## Recent Code Fixes (2025-09-21)

### Telegraph Document Sending Issue Resolution
**Problem**: `Call to undefined method DefStudio\Telegraph\Telegraph::caption()` error when sending documents with captions.

**Root Cause**: The `TelegramBot::sendDocument()` method was using `->caption($caption)` which is not a valid method in the DefStudio Telegraph library.

**Solution Applied**:
- **File**: `app/Services/Messaging/TelegramBot.php` (line 493)
- **Change**: Replaced `->caption($this->ensureUtf8($caption))` with `->withData('caption', $this->ensureUtf8($caption))`
- **Method**: `sendDocument()` method now correctly uses the Telegraph library's `withData()` method for adding captions

**Code Before**:
```php
$telegraph->document($filePath)->caption($this->ensureUtf8($caption))->send();
```

**Code After**:
```php
$telegraph->document($filePath)->withData('caption', $this->ensureUtf8($caption))->send();
```

**Impact**: 
- ✅ Document sending with captions now works correctly
- ✅ Legal contract files (`/قولنامه` command) can be sent with proper captions
- ✅ No breaking changes to existing functionality
- ✅ Follows DefStudio Telegraph library documentation standards

### Legal Contract Files Path Resolution
**Problem**: Contract files were located in project root `contracts/` directory but `sendContractFiles` method was looking in `storage/app/contracts/`.

**Solution Applied**:
- **Action**: Copied contract files from `contracts/` to `storage/app/contracts/` directory
- **Files Moved**: 
  - `sample_contract.pdf` (744 bytes)
  - `buy_contract_template.txt` (968 bytes) 
  - `sell_contract_template.txt` (968 bytes)
  - `sample_contract.txt` (1517 bytes)
- **Result**: Contract delivery system now has access to all required files

### Legal Disclaimer Message Update
**Problem**: Inconsistent and potentially misleading legal disclaimer messages.

**Solution Applied**:
- **File**: `app/Http/Controllers/TelegramWebhookController.php`
- **Constant Updated**: `LEGAL_DISCLAIMER` changed from "⚠️ این پاسخ مشاوره حقوقی رسمی نیست و جایگزین وکیل یا قرارداد مکتوب نمی‌شود." to "📋 این قولنامه توسط یک وکیل مجرب نوشته شده است. با پرینت گرفتن می‌توانید استفاده کنید."
- **Message Consistency**: All hardcoded disclaimer messages in `sendContractFiles` method now use `self::LEGAL_DISCLAIMER` constant
- **Impact**: Unified, professional legal messaging across all contract-related communications
