Commit Graph
297 Commits
Author SHA1 Message Date
hamed 70d280070b Add migration to include site_name column in cities table 2026-07-02 10:09:14 +03:30
hamed a7d5d17b9c feat(cors): update CORS settings for multi-domain production support 2026-07-01 22:57:16 +03:30
hamed cf38faff88 feat: add SmokeEndpointsCommand for endpoint health checks and logging
- Introduced SmokeEndpointsCommand to send HTTP requests to all /api and /oauth endpoints on the live site, ensuring logs are generated in the app_log table.
- Implemented non-destructive testing by using real IDs for GET requests and non-existent IDs for write operations.
- Added functionality to authenticate as an admin using a fixed OTP for development.
- Created tests for endpoint sweeps to verify that no 5xx errors occur during various conditions, including no-auth and wrong-role scenarios.
- Established RouteManifest for public success paths that should return 200 without requiring fixtures.
2026-07-01 22:37:53 +03:30
hamed 7814bcc0de feat(logging): implement log pruning functionality
- Add LogPruneService to handle the deletion of old logs based on retention settings.
- Create PruneLogsCommand to provide a console command for log pruning.
- Introduce PruneLogsMessage and PruneLogsHandler for message handling related to log pruning.
- Update the AST cache with new classes and their relationships.
2026-07-01 21:50:51 +03:30
hamed a31e8b4314 Add AST JSON files for doctor service, tag controller, and SMS log entities
- Created new AST JSON file for the doctor service API documentation, detailing endpoints, parameters, and responses.
- Added AST JSON file for the TagController, including methods and their relationships with imported classes.
- Introduced AST JSON file for the SmsLog entity, outlining its methods and dependencies.
2026-07-01 14:21:08 +03:30
hamed 3120132274 fix(footer): update copyright year in footer to 2024 2026-07-01 13:21:26 +03:30
hamed dfc86391c4 feat(payment): enhance payment configuration to include active gateways and update CORS settings 2026-07-01 12:45:18 +03:30
hamed e1eae1099c feat(audit): add comprehensive QA audit report for ClinicPro with findings and recommendations 2026-07-01 12:26:02 +03:30
hamed b366baa64c feat(sms): simplify SMS settings to use only KAVENEGAR_API_KEY from env, remove other SMS-related fields from config 2026-07-01 11:53:16 +03:30
hamed 9d8d1014f1 feat: update README to include category seeding instructions 2026-06-30 22:15:45 +03:30
hamed cc193c866b Add manifest.json for graphify-out documentation with metadata for various files 2026-06-30 22:12:26 +03:30
hamed 9eb5a03258 feat: add CategoryImporter service and SeedCategoriesCommand for seeding category data
- Implemented CategoryImporter service to handle bulk export/import logic for categories.
- Created SeedCategoriesCommand to seed category tables from JSON files in data/seed/.
- Added validation and normalization for category data during import.
- Ensured proper error handling and user feedback during the seeding process.
2026-06-30 21:58:44 +03:30
hamed 22937dfa56 feat: add CategoryImportController for bulk JSON import and export of categories
- Implemented export functionality to retrieve all rows from specified category tables.
- Developed import functionality with strict validation and referential integrity checks.
- Added error handling for various import scenarios including invalid formats and duplicate entries.
- Introduced tests for import functionality to ensure correct behavior and validation.
2026-06-30 21:51:06 +03:30
hamed 803196108c feat(logging): Implement database logging with app_log table
- Created migration to set up app_log table for storing application logs.
- Added AppLog entity and repository for ORM handling of logs.
- Developed DbLogger service to persist logs of level WARNING and above to the database while maintaining existing logging behavior.
- Implemented tests for admin log retrieval and DbLogger functionality to ensure proper logging behavior.
- Enhanced logging context sanitization for better error tracking.
2026-06-29 20:01:03 +03:30
hamed 830f7e8d0c Refactor SMS Provider Classes to Support Nullable API Key and Sender
- Updated KavehNegarProvider and RanginehProvider constructors to accept nullable API key and sender parameters.
- Adjusted methods in both providers to handle cases where the API key or sender may not be set, preventing potential TypeErrors.
- Modified community IDs in graph.json for various entities to reflect updated associations.
- Updated manifest.json with new modification times and AST hashes for KavehNegarProvider and RanginehProvider.
- Added new AST cache files for KavehNegarProvider and RanginehProvider reflecting the latest structure and relationships.
2026-06-29 19:31:42 +03:30
hamed afb5b28272 Update community IDs in graph.json and manifest.json; modify migration command in liara_pre_start.sh; enhance error logging in ExceptionSubscriber.php; add new AST cache files. 2026-06-29 19:29:10 +03:30
hamed 97958ecc7d Update Kernel.php for writable base handling, add .htaccess for routing, and modify manifest.json and liara.json
- Refactored Kernel.php to ensure writable base directory is set correctly for production on Liara.
- Added .htaccess file to forward requests to the public directory, addressing the limitation of Liara's PHP platform.
- Updated manifest.json with new modification times and AST hashes for affected files.
- Minor formatting change in liara.json to ensure proper file structure.
2026-06-29 18:24:48 +03:30
hamed c71d674d22 Add new JSON files for documentation and scripts in the graphify cache
- Created JSON file for admin SPA test suite with detailed nodes and edges.
- Added JSON file for Liara deployment documentation, including various steps and references.
- Introduced JSON files for Liara pre-build and pre-start scripts, detailing their structure and entry points.
- Included an empty JSON file indicating skipped data for non-config/manifest content.
2026-06-29 17:38:05 +03:30
hamed 9bff63bf5b fix: increase health check interval to improve reliability 2026-06-29 17:19:50 +03:30
hamed 60a62489d5 feat: add health check configuration to Liara deployment 2026-06-29 17:07:06 +03:30
hamed a3dcb92669 fix: update application name and paths in Liara configuration 2026-06-29 17:03:26 +03:30
hamed b4273cfa8b feat(deploy): configure deployment for ClinicPro on Liara PHP platform 2026-06-29 16:11:54 +03:30
hamed efee966efb feat(deploy): add deployment configuration for Liara with Docker and Supervisor 2026-06-29 15:03:30 +03:30
hamed 75dcf0d2a8 refactor: remove create_test_users.php script to streamline user creation process 2026-06-28 23:02:17 +03:30
hamed 6084dc5d6b feat: add comprehensive tests for UI components, hooks, and API interactions
- Implement tests for Pagination, StatusBadge, ConfirmDialog, and MobileInput components.
- Add tests for useSubscription, usePaymentConfig, and usePwaInstall hooks.
- Create tests for API requests in the api module, including success and error handling.
- Add utility function tests for formatting and validating Iranian mobile numbers.
- Implement tests for BlogFormPage and BlogsPage to validate form submissions and data fetching.
- Add tests for LoginPage to ensure proper validation and state management.
- Create tests for authStore and uiStore to validate state management and functionality.
- Set up Vitest configuration and testing utilities for consistent testing environment.
2026-06-28 22:58:15 +03:30
hamed 439bb0868e Add SVG for Open Graph image featuring brand and service details 2026-06-28 22:34:28 +03:30
hamed 1bf9035f5f feat(seo): add SEO controller for robots.txt and sitemap.xml
- Implemented SeoController with methods for serving robots.txt and sitemap.xml.
- Updated robots.txt logic to disallow access to /admin and /api paths.
- Enhanced meta tags and Open Graph data in home.html.twig for better SEO.
- Added X-Robots-Tag header in SecurityHeadersSubscriber for /admin and /api routes.
2026-06-28 22:28:07 +03:30
hamed 89451e8d3b fix(home): hide download links and update section titles for better UX 2026-06-28 22:18:01 +03:30
hamed a153bded9b Add AST JSON representation for insurance API documentation 2026-06-28 22:13:12 +03:30
hamed 8705b88270 fix(auth): update refresh token behavior to be reusable within TTL and add tests for token functionality 2026-06-28 21:57:42 +03:30
hamed c2b47d6396 delete(tests): remove RefreshTokenRotationTest as it is no longer needed 2026-06-28 21:52:52 +03:30
hamedandClaude Opus 4.8 2f3f25f48e fix(admin): paginate claims & settlement lists for backend-audit 50-cap
GET /billing/claims and GET /settlement now return only the first 50 rows by
default (data.data unchanged, data.meta added). ClaimsPage and
RepresentationSettlementPage read the full array with no pager, so rows beyond 50
were unreachable. Add page state + ?page/limit + the existing <Pagination>
(reading data.meta.totalRecords).

No change needed for: 422 on claim approve/pay (api.ts already surfaces the
backend message via toast; the admin UI sends no amount so it's unreachable),
the owner-only appointment-settings endpoints (admin user bypasses), and refresh
rotation (authStore.refresh already persists the rotated refresh_token).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:49:44 +03:30
hamedandClaude Opus 4.8 2764e68e60 ci: phpstan baseline + GitHub Actions workflow (E5, E4)
E5: generate phpstan-baseline.neon (the 41 pre-existing errors) and include it,
so `phpstan analyse` returns OK and the gate now fails only on NEW errors. The
baseline is meant to be burned down over time.

E4: add .github/workflows/ci.yml — MariaDB 11.8 + redis services, composer
install, JWT keygen, phpstan (baseline-clean), migrate-on-empty-db smoke, and
phpunit. Locally verified the substantive checks: a fresh empty DB migrates
cleanly to 64 tables (guards the "migrations break on empty DB" bug class),
phpstan is green, and the 70-test suite passes. The GitHub Actions service
wiring itself is first-run-pending (can't be exercised offline).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:25:09 +03:30
hamedandClaude Opus 4.8 66ab597efd fix(audit): low-tier — session patient-gate + 201 statuses (L1, L11) + triage
L1: PATCH /session now enforces the patient_records subscription gate like its
sibling endpoints (ownership was already checked; the feature gate was missing).
L11: POST /pre-registration and POST /representation/iban return 201 on create.

Remaining low-tier findings triaged and accepted without change (documented in
docs/audit-backlog.md): L8 is a false positive (FK auto-indexed), L6/L7/L9 are
marginal indexes, L4/L5 are small bounded N+1, L2/L3/L10/L12 are minor — none
with security/integrity impact.

Regression: tests/Audit/LowTierFixesTest (both fail without the fix).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:21:03 +03:30
hamedandClaude Opus 4.8 b1aaaf3d55 fix(db): purge service-item config rows on delete (M20)
Tariff and TenantServiceCoverage reference a service item by a raw int (no FK),
so deleting an item orphaned its tariffs and tenant-coverage config. Delete them
in deleteItem() before removing the item. (The in-use FK guard for invoice/claim
usage is preserved.)

Remaining M20 refs (ClinicStaff/SmsWallet/DoctorAddress.clinicId/Claim.insurance_id
on rare owner deletions) are accepted as harmless unreferenced rows; SmsWallet is
intentionally retained as a financial record. Documented in docs/audit-backlog.md.

Regression: tests/ClinicService/ServiceItemDeleteCleanupTest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:17:30 +03:30
hamedandClaude Opus 4.8 4b80616a17 refactor(errors): centralise legacy string error codes in ErrorCodes (M21)
~27 ad-hoc error codes (SLOT_TAKEN, USER_NOT_FOUND, VALIDATION, …) were raw
strings, so ErrorCodes::message() returned the "unknown" fallback for them.
Register all 14 distinct codes as constants with their messages and replace the
raw usages across AdminApiController, MyAppointmentsController, CategoryController,
PreRegistrationController and ClinicInvitationController.

Wire values are kept identical (verified no consumer — admin SPA, nobat724_front,
tauri — switches on these strings), so this is backward compatible.

Regression: tests/Shared/ErrorCodesTest (wire values preserved + message resolves).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:12:23 +03:30
hamedandClaude Opus 4.8 96a86dbfed fix(db): business-key unique constraints (M16-M19)
Add unique constraints (one migration, no dup data in either DB):
- users.email, users.national_code (M16) — NULLs still allowed.
- payments.gateway_token (M17).
- date_overrides (doctor_id, date) (M18) — was a non-unique index.
- financial_breakdowns (payment_id, source) (M19) — anti double-accounting.

Regression: tests/Database/UniqueConstraintsTest (4 duplicate-insert cases).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 21:03:54 +03:30
hamedandClaude Opus 4.8 ccb71e4371 perf(secretary,billing): kill N+1 in secretary + claims lists (M8, M9)
M8: DoctorSecretary::toArray() lazy-loaded secretary/doctor/clinic per row;
fetch-join them in findByDoctorScope/findByClinic (shared listWithRelations()).

M9: enrichClaims() lazy-loaded each claim's items collection and called
insuranceRepo->find() per claim. Fetch-join items in findByTenant (Paginator,
fetchJoinCollection) and batch-fetch insurance names once.

Regressions (query count constant vs row count): SecretaryListNPlusOneTest,
ClaimsListNPlusOneTest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:43:35 +03:30
hamedandClaude Opus 4.8 61ac775175 perf(settlement,rating): paginate 3 unbounded list endpoints (M10-M12)
- M10 GET /settlement: was unbounded; add page/limit + countByUser + data.meta.
- M11 GET /admin/comments/pending: paginate findPending + countPending.
- M12 GET /comments/{doctor}: paginate the fetch-joined roots query via
  Paginator(fetchJoinCollection) + countApprovedRootsByDoctor.

All keep the existing { data: { data: [...] } } envelope and add data.meta
(backward compatible). Default limit 50 / max 100.

Regressions: SettlementListPaginationTest, CommentPaginationTest (both fail
without the limits). Also de-flaked SendCodeMobileRateLimitTest (randomised the
IP block so the persistent per-IP limiter buckets don't accumulate across runs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:36:41 +03:30
hamedandClaude Opus 4.8 447482c2ea perf(db): composite index on wallet_transactions(user_id, type) (M14)
Helps the per-type SUM balance query. M13 (service_items.section_id) and M15
(clinic_doctor_invitations.doctor_id) were false positives — both columns carry
a FK and are therefore auto-indexed by InnoDB; verified against the live schema.

Structural regression: InfraSmokeTest::testWalletUserTypeIndexExists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:28:24 +03:30
hamedandClaude Opus 4.8 670cef24f4 fix(security): per-mobile OTP cap + refresh-token rotation (M6, M7)
M6: send-code rate-limited only per IP, so a victim's number could be
SMS-flooded from rotating IPs. Add a per-mobile bucket (same 5/hour policy)
keyed by the validated mobile.

M7: /oauth/token/refresh reused the presented refresh token verbatim (no
rotation) and never re-checked the user. The rotation infra already existed
(issueTokens mints a fresh refresh token) — the controller just discarded it.
Now revoke the presented token (single-use), issue a fresh pair, and reject a
suspended user (status != 1).

Regressions: tests/Auth/SendCodeMobileRateLimitTest,
tests/Auth/RefreshTokenRotationTest (both fail without the fix).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:25:22 +03:30
hamedandClaude Opus 4.8 fe6383314e fix(security): enforce ownership on 4 IDOR read/bind endpoints (M2-M5)
- M2 GET /insurance/{id}: was unguarded; now owner-or-admin (403 otherwise) —
  stops reading another doctor's negotiated price by id enumeration.
- M3 GET /clinic-pro/doctor-address/{id}: add the same owner/admin check the
  sibling PATCH/DELETE already had.
- M4 POST/PATCH /service-item: staff_uuid must belong to the caller's tenant
  (entity_type/entity_id) → 422; stops binding another tenant's staff.
- M5 appointment-settings list endpoints (date-override/holidays/
  available-locations): add the per-doctor ownership check the sibling
  single-record endpoints already enforce.

Regressions (6 negative cases fail without the fixes):
DoctorInsuranceOwnershipTest, DoctorAddressOwnershipTest,
ServiceItemStaffOwnershipTest, AppointmentSettingsListOwnershipTest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:17:52 +03:30
hamedandClaude Opus 4.8 23ca56b293 fix(billing): bound approved/paid amounts on claim transition (M1)
approve/pay accepted any approved_rials/paid_rials with no bounds, so the
claiming tenant could write arbitrary figures into the insurer-debt ledger
(negative, or far above the claimed total). Validate: approved ∈ [0, claimed],
paid ∈ [0, approved] → 422 otherwise. (The "force arbitrary status" half of the
finding was already prevented by Claim::canTransitionTo.)

Regression: tests/Billing/ClaimAmountBoundsTest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:07:19 +03:30
hamedandClaude Opus 4.8 131a78343b test(audit): strengthen H5/H7/H8 into true fail-without-fix regressions
Verification pass found three tests only guarded correctness, not the fix's
behavior:
- H7: add repository white-box test asserting likes/replies come back as
  initialised PersistentCollections (lazy without the fetch-join).
- H8: add a query-count test (constant vs coverage-row count) — without the
  batch fetch the count grows ~1 per row.
- H5: add an end-to-end test hitting DELETE /api/v1/doctor and asserting the
  insurance config is purged (the service unit test didn't cover the wiring).

All three now fail when their fix is reverted. Suite: 39 tests / 92 assertions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 20:01:12 +03:30
hamed c9ae3882fe docs(audit): mark Critical+High tier complete in backlog 2026-06-28 19:42:39 +03:30
hamedandClaude Opus 4.8 d8f7db0ada perf(billing,settlement): paginate claims and wallet transactions (H9, H10)
GET /billing/claims loaded every tenant claim with no limit. Add
findByTenant(page, limit) + countByTenant (shared query builder), default
limit 50 / max 100, and expose totals as data.meta — kept inside the existing
{ data: { data: [...] } } envelope so current clients are unaffected.

GET /wallet/transactions was already bounded (findByUser defaulted to limit 50)
but page-less; add page/offset + countByUser + the same additive meta.

Regression: tests/Billing/ClaimsListPaginationTest,
tests/Settlement/WalletTransactionsPaginationTest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:42:04 +03:30
hamedandClaude Opus 4.8 42d934abc2 perf(rating): fetch-join comment tree to kill N+1 in public list (H7)
findApprovedRootsByDoctor used a plain findBy, so Comment::toArray() lazy-loaded
likes, replies and the author per comment (and recursively per reply). Hydrate
in two fetch-join passes (roots + author + likes; then replies + their author +
likes + one further reply level) — no per-comment lazy loads for a two-level
thread.

Regression: tests/Rating/CommentListNPlusOneTest (functional correctness — like
counts, approved-only replies, author preserved).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:35:34 +03:30
hamedandClaude Opus 4.8 eb1997066e perf(insurance): batch-fetch service items in coverage list (H8)
listServiceCoverage called serviceItemRepo->find() once per coverage row (N+1).
Collect the ids and fetch them in one findBy(['id' => $ids]), then map by id.

Regression: tests/Insurance/ServiceCoverageNPlusOneTest (functional correctness —
every row resolves the right service_item_uuid).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:29:06 +03:30
hamedandClaude Opus 4.8 6b12f3ddb9 perf(doctor): fetch-join specialties in clinic doctor list (H6)
findByClinicWithFilters left-joined specialties only for filtering, so
toListArray() lazy-loaded them per doctor (N+1). addSelect them and switch the
result fetch to Paginator(fetchJoinCollection: true) so LIMIT still paginates by
doctor.

Test infra: ApiTestCase::countQueries() (via doctrine.debug_data_holder).
Regression: tests/Doctor/ClinicDoctorListNPlusOneTest asserts the query count
does not grow with doctor count (4→10 without the fix).

Also relaxed AppointmentExpiryServiceTest's exact-count assertion (it counts all
stale pendings in the shared db_test, which accumulates) — logged test-isolation
debt as E6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:24:49 +03:30
hamedandClaude Opus 4.8 4cf6873900 fix(db): purge insurance config when a doctor/clinic is deleted (H5)
tenant_insurances, entity_insurance_pricing and tenant_service_coverages
reference their owner through a polymorphic (entity_type, entity_id) pair, so no
database FK can cascade their cleanup. Hard-deleting a doctor (DoctorController)
or clinic (AdminApiController) left these rows orphaned.

Add TenantInsuranceCleanupService::purgeForEntity() and call it from both delete
paths — removes coverage (via owning tenant_insurance ids), then tenant
insurances, then pricing.

Residual (separate, lower-freq paths): deleting an insurance category or a
service_item still orphans rows that reference them by id — tracked under the
medium-tier soft-ref findings.

Regression: tests/Insurance/TenantInsuranceCleanupTest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:16:10 +03:30