Files
orderPRO/.paul/codebase/CONCERNS.md

265 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Codebase Concerns
**Analysis Date:** 2026-03-12
---
## Tech Debt
### [MEDIUM] SonarQube — 327 Open Issues (2026-03-12)
Listed in `DOCS/todo.md`:
- 95x `php:S112` — generic `new \Exception` instead of typed exceptions
- 57x `php:S1142` — excessive `return` statements per method
- 40x `php:S1192` — repeated string literals not extracted to constants
- 31x `php:S3776` — high cognitive complexity
- 11x `php:S1448` — classes with too many methods (god classes)
- 4x `php:S138` — methods over the allowed line limit
The most critical from a maintainability standpoint are the complexity and god-class violations.
---
### [LOW] Duplicate Migration Number `000014`
- Issue: Two migration files share sequence number `000014`:
- `20260227_000014_create_product_integration_translations.sql`
- `20260301_000014_add_products_sku_format_setting.sql`
- File: `database/migrations/`
- Impact: Depending on how the migrator detects "already run" (by filename vs. sequence number), one of these may silently never execute or may fail.
- Fix approach: Rename one to `000014b` or renumber all subsequent ones. Verify migrator uses full filename as key.
---
## Security Concerns
### [HIGH] No SSL Verification on cURL Calls
- Issue: None of the `AllegroApiClient`, `ShopproApiClient`, `ApaczkaApiClient`, or `AllegroOAuthClient` set `CURLOPT_SSL_VERIFYPEER` or `CURLOPT_SSL_VERIFYHOST`. PHP's default is to verify SSL, but explicitly not setting it means behavior depends on the server's `php.ini` `curl.cainfo` configuration.
- Files:
- `src/Modules/Settings/AllegroApiClient.php`
- `src/Modules/Settings/AllegroOAuthClient.php`
- `src/Modules/Settings/ShopproApiClient.php`
- `src/Modules/Settings/ApaczkaApiClient.php`
- Impact: On environments without a properly configured CA bundle (common on shared hosting / Windows XAMPP), all API calls may proceed without SSL verification, enabling MITM attacks on OAuth tokens and API keys.
- Fix approach: Explicitly set `CURLOPT_SSL_VERIFYPEER => true` and configure `CURLOPT_CAINFO` pointing to a known CA bundle. Document this in `.env.example`.
---
### [MEDIUM] CSRF Token Is Never Rotated After Login
- Issue: `Csrf::token()` in `src/Core/Security/Csrf.php` generates the token once per session and never regenerates it. The token is never rotated after a successful login, and it is never invalidated after a POST form is submitted.
- File: `src/Core/Security/Csrf.php`
- Impact: If a CSRF token leaks (e.g., via Referer header, browser history), it remains valid for the entire session lifetime.
- Fix approach: Rotate the CSRF token on login. Optionally regenerate after each use (per-form tokens). At minimum, invalidate the session token on `AuthService::logout()`.
---
### [MEDIUM] Label File Served with `file_get_contents($fullPath)` — Partial Path Traversal Risk
- Issue: In `ShipmentController::label()`, the file path is built from `$package['label_path']` retrieved from the database. While `basename()` is used for the filename in the response header, `$fullPath` itself is derived by concatenating `$storagePath . '/labels/' . $filename` from the shipment service. There is no `realpath()` check confirming the file is actually within the storage directory before serving it.
- File: `src/Modules/Shipments/ShipmentController.php` (lines 292316)
- Impact: Low risk currently (path comes from DB, not user input directly), but if `label_path` column is ever injectable or incorrectly set, arbitrary file reads become possible.
- Fix approach: Validate `realpath($fullPath)` starts with `realpath($storagePath)` before calling `file_get_contents()`.
---
### [LOW] Allegro OAuth Callback Endpoint Has No Auth Middleware
- Issue: `GET /settings/integrations/allegro/oauth/callback` is registered without `$authMiddleware`. The OAuth state parameter is validated via session, which does provide CSRF protection for the OAuth flow, but any authenticated check is absent.
- File: `routes/web.php` (line 239)
- Impact: An unauthenticated actor with knowledge of the state parameter could complete an OAuth token exchange. In practice, the state must match the session, limiting exploitability to session-fixation scenarios.
- Fix approach: Add `$authMiddleware` to the callback route. Handle the "not authenticated yet" edge case by storing the session state before redirect.
---
## Performance Concerns
### [HIGH] N+1 Correlated Subqueries on Every Order List Row
- Issue: The `OrdersRepository::paginate()` list SQL includes three correlated subqueries per row: `items_count`, `items_qty`, `shipments_count`, and `documents_count`. These fire once per order in the result set.
- File: `src/Modules/Orders/OrdersRepository.php` (lines 124127)
- Impact: For a page of 50 orders: 4 × 50 = 200 additional subquery executions per page load. Degrades significantly as the `orders` table grows.
- Fix approach: Replace correlated subqueries with LEFT JOIN + GROUP BY aggregates, or preload counts in a single separate query (`SELECT order_id, COUNT(*) ... GROUP BY order_id WHERE order_id IN (...)`).
---
### [HIGH] `canResolveMappedMedia()` Queries `information_schema.COLUMNS` on Every Request
- Issue: `OrdersRepository::canResolveMappedMedia()` runs a query against `information_schema.COLUMNS` to check whether optional product mapping tables exist. It caches the result in a private property, but this property is instance-scoped — the check fires once per HTTP request that instantiates `OrdersRepository`.
- File: `src/Modules/Orders/OrdersRepository.php` (lines 604648)
- Impact: `information_schema` queries are notoriously slow on MySQL. This fires on every order-related page load (list, detail, shipment).
- Fix approach: Cache the result in a static class property or in the app-level cache/session so it survives across requests. Alternatively, remove the check entirely — if the schema is managed by migrations, the tables either exist or don't.
---
### [MEDIUM] AllegroStatusSyncService Fetches Up to 50 Orders Then Re-Imports Each One Fully
- Issue: `AllegroStatusSyncService::findOrdersNeedingStatusSync()` returns up to 50 Allegro orders. For each, `importSingleOrder()` is called, which makes a full Allegro API call per order (checkout-form fetch + optional shipments fetch + optional offer image fetch per line item). This is 50+ API calls per cron run.
- File: `src/Modules/Settings/AllegroStatusSyncService.php`
- Impact: Rate-limit exhaustion on Allegro API. Slow cron runs.
- Fix approach: Use a lighter-weight API call to fetch only status fields for multiple orders (e.g., `listCheckoutForms` with a filter) rather than a full re-import per order.
- Note (2026-03-13): Plan 02-02 dodał kursor `last_status_checked_at` — eliminuje re-import zamówień bez zmiany statusu. Full API call per order pozostaje osobnym concernem.
---
### [MEDIUM] No Database Indexes Verified for Key Queries
- Issue: Queries frequently filter and sort on `orders.source`, `orders.external_status_id`, `orders.ordered_at`, `order_items.order_id`, and `allegro_order_status_mappings.allegro_status_code`. Whether indexes exist for these columns is not documented.
- Files: `src/Modules/Orders/OrdersRepository.php`, `database/migrations/20260302_000018_create_orders_tables_and_schedule.sql`
- Impact: Full table scans as data grows.
- Fix approach: Audit the migration that creates `orders` and related tables for index definitions. Add missing indexes for `(source, external_status_id)`, `(ordered_at)`, and `allegro_status_code`.
---
## Architectural Concerns
### [HIGH] `Settings` Module Is a God Module — Contains Unrelated Concerns
- Issue: The `src/Modules/Settings/` directory contains 30+ classes covering: Allegro integration, Apaczka integration, InPost integration, shopPRO integration, company settings, cron settings, order statuses, carrier mappings, delivery method mappings, and OAuth. These are logically distinct domains crammed into a single module namespace.
- Files: All files in `src/Modules/Settings/`
- Impact: The module boundary is meaningless. New integrations will continue to pile into Settings. `AllegroIntegrationController` (923 lines) and `ShopproIntegrationsController` (901 lines) are god classes by SonarQube rules (S1448).
- Fix approach: Gradually decompose into purpose-built modules: `src/Modules/Integrations/Allegro/`, `src/Modules/Integrations/Shoppro/`, `src/Modules/Integrations/Apaczka/`, `src/Modules/Integrations/Inpost/`.
---
### [HIGH] `AllegroIntegrationController` and `ShopproIntegrationsController` Are God Classes (900+ lines)
- Issue: Each controller handles: displaying settings, saving credentials, OAuth flow, status mapping CRUD, delivery method mapping CRUD, and import triggering. Each is 900+ lines.
- Files:
- `src/Modules/Settings/AllegroIntegrationController.php` (923 lines)
- `src/Modules/Settings/ShopproIntegrationsController.php` (901 lines)
- Impact: Violates SRP. Methods average far more than 50 lines. Cognitive complexity is high (SonarQube S3776, 31 violations).
- Fix approach: Split each into focused controllers per tab/concern: e.g., `AllegroCredentialsController`, `AllegroStatusMappingController`, `AllegroDeliveryMappingController`.
---
### [HIGH] `ShopproOrdersSyncService` Is 1192 Lines — Largest Single File
- Issue: `ShopproOrdersSyncService` handles the full import lifecycle for shopPRO orders including: pagination, cursor management, order mapping, address building, item building, image resolution, payment mapping, shipment mapping, status history, and activity logging — all in one class.
- File: `src/Modules/Settings/ShopproOrdersSyncService.php`
- Impact: Highest complexity class in the codebase. Hard to test, hard to modify, prone to regression.
- Fix approach: Extract a `ShopproOrderMapper` (mapping logic), a `ShopproImageResolver` (image fetch), and keep `ShopproOrdersSyncService` as the orchestrator. Mirror the pattern of `AllegroOrderImportService` which is better separated.
---
### [MEDIUM] No Dependency Injection Container — Full Object Graph in `routes/web.php`
- Issue: `routes/web.php` manually constructs all services and controllers, including multiple separate instantiations of identical objects (e.g., `new AllegroApiClient()` appears 3 times, `new OrdersRepository($app->db())` appears 4 times).
- File: `routes/web.php`
- Impact: Multiple object instances where singletons are implied. Adding new dependencies requires editing this file, which is already 257 lines. Risk of silently passing the wrong instance.
- Fix approach: Introduce a lightweight DI container or service locator. At minimum, hoist shared instances (`$allegroApiClient`, `$ordersRepository`) to named variables and reuse them.
---
## Incomplete Features
### [HIGH] 5 Stub Buttons with No Actions in Order Detail View
- Issue: The order detail page (`resources/views/orders/show.php`, lines 4853) contains 5 buttons that render in the UI but have no `href`, no form action, and no JavaScript handler: "Strefa klienta", "Platnosc", "Drukuj", "Pakuj", "Edytuj".
- File: `resources/views/orders/show.php`
- Impact: Users see clickable but non-functional UI elements. The "Pakuj" (pack) button is styled as the primary CTA (`btn--primary`).
- Fix approach: Either implement the features or hide the buttons until they are ready.
---
### [HIGH] `orderpro_to_allegro` Status Sync Direction Not Implemented
- Issue: The setting UI offers two sync directions: Allegro → orderPRO and orderPRO → Allegro. The second direction explicitly returns early with "Kierunek orderPRO -> Allegro nie jest jeszcze wdrozony" ("not yet implemented").
- File: `src/Modules/Settings/AllegroStatusSyncService.php` (lines 3845)
- Impact: Users who configure the "push status to Allegro" direction get silent no-op behavior with no clear user-facing indication in the cron log.
- Fix approach: Either implement the feature or disable the `orderpro_to_allegro` direction option in the UI until it is ready.
---
### [HIGH] TODO in `DOCS/todo.md` — UI Issues Open
The following items from `DOCS/todo.md` are marked incomplete:
- Item 12: Status change sync with Allegro after manual user change
- Item 14: Input/select/textarea border color needs to be darker
- Item 15: Source and ID display order should be reversed (source first, then ID)
- Item 16: Order list statuses should be colored according to settings
- Item 17: Source display should show specific integration name instead of "shopPRO"; ID label missing
- Files: `resources/views/orders/list.php`, `resources/views/orders/show.php`
---
### [MEDIUM] InPost Integration — Settings Page Exists But No Shipment Provider
- Issue: `InpostIntegrationController` and `InpostIntegrationRepository` exist and the settings page at `/settings/integrations/inpost` is functional. However, there is no `InpostShipmentService` implementing `ShipmentProviderInterface`. InPost labels are currently routed through `allegro_wza` as a workaround (`provider_code === 'inpost'` is remapped to `'allegro_wza'` in `ShipmentController::create()`).
- Files:
- `src/Modules/Settings/InpostIntegrationController.php`
- `src/Modules/Settings/InpostIntegrationRepository.php`
- `src/Modules/Shipments/ShipmentController.php` (line 165166)
- Impact: InPost credentials are stored but not used by their own provider. The remap to `allegro_wza` means InPost shipments are actually created via Allegro WZA. This breaks if the user has only InPost credentials and no Allegro integration.
- Fix approach: Implement `InpostShipmentService implements ShipmentProviderInterface` using the stored InPost API token.
---
### [MEDIUM] `database/drafts/` Contains Uncommitted Schema SQL
- Issue: `database/drafts/20260302_orders_schema_v1.sql` exists in the `drafts` subdirectory. This is not a migration — it will not be executed by the migrator.
- File: `database/drafts/20260302_orders_schema_v1.sql`
- Impact: Unclear whether this represents schema that has not yet been migrated or schema that was migrated by other means. Creates confusion about source of truth.
- Fix approach: Either move to `database/migrations/` with a proper sequence number, or delete it if already superseded.
---
## Known Bugs
## Test Coverage Gaps
### [HIGH] No Integration or Functional Tests Exist
- Issue: The `tests/` directory contains only `bootstrap.php`. No test files exist. The `archive/` directory contains unit tests from a previous reset, but they are not in active use.
- Files: `tests/bootstrap.php`, `archive/2026-03-02_users-only-reset/tests/`
- Risk: Zero automated coverage for any critical path: order import, shipment creation, OAuth flow, cron sync, status updates.
- Priority: High
---
### [HIGH] Allegro OAuth Token Refresh Logic Has No Tests
- Issue: The token refresh logic is centralized in `AllegroTokenManager` but untested. It includes complex edge cases: token expiry within 5-minute window, empty refresh token fallback, write-then-re-read pattern.
- Files: `src/Modules/Settings/AllegroTokenManager.php`
- Risk: Token refresh failures cause complete import failure. Silent breakage possible.
- Priority: High
---
### [MEDIUM] No Error Path Tests for Order Import
- Issue: `AllegroOrderImportService::importSingleOrder()` and `ShopproOrdersSyncService::sync()` have complex error handling (401 retry, silent Throwable catch blocks). None of this is tested.
- Files:
- `src/Modules/Settings/AllegroOrderImportService.php`
- `src/Modules/Settings/ShopproOrdersSyncService.php`
- Risk: Error paths that swallow exceptions silently (multiple `catch (Throwable) {}` blocks in `OrdersRepository`) may hide data integrity issues.
- Priority: Medium
---
## Fragile Areas
### [HIGH] `AllegroStatusSyncService` — `orderpro_to_allegro` Silent No-Op
- Files: `src/Modules/Settings/AllegroStatusSyncService.php`
- Why fragile: Returns `ok: true` when direction is `orderpro_to_allegro` with zero processing, no log, no user feedback. Cron logs will show "success" even though nothing happened.
- Safe modification: Do not add code after the early return without understanding why it was left as a no-op. Consider returning `ok: false` or a warning until the feature is implemented.
- Test coverage: None.
---
### [MEDIUM] Web Cron Throttling Uses Session Timestamp (`$_SESSION['cron_web_last_run_at']`)
- Issue: `Application::isWebCronThrottled()` reads and writes a cron timestamp from `$_SESSION`. In stateless or load-balanced deployments, different users hit different sessions, meaning the throttle is per-session (per-user), not global.
- File: `src/Core/Application.php` (lines 363375)
- Why fragile: With two active sessions, the cron may run twice within the throttle window. The DB-level `GET_LOCK` is the real protection, but this creates confusing interactions.
- Fix approach: Move last-run timestamp to the database (e.g., an `app_settings` key) instead of the session.
---
*Concerns audit: 2026-03-12*