Commit Graph
240 Commits
Author SHA1 Message Date
hamed e456809b55 fix(devops): repair phpstan config so static analysis runs again
- container_xml_path -> containerXmlPath (phpstan-symfony v2 rename); the old
  key made phpstan abort with an invalid-configuration error, so analysis
  silently never ran
- add the missing tests/doctrine_object_manager.php loader
- drop a stale ignoreErrors pattern

phpstan now runs and surfaces 42 pre-existing level-5 errors (tracked separately).
2026-06-28 17:17:46 +03:30
hamed 8b96753df6 perf(db): add missing indexes (appointment expiry, session dates, user status)
- appointments(status, expires_at): the per-minute expiry scheduler filtered on
  these with no covering index
- patient_sessions(created_at): dashboard revenue range scans
- users(status): admin user-list filter

PatientRecord(entity_type,entity_id) already covered by its unique constraint;
UserActiveContext.user_id is the PK — neither needed a new index.
2026-06-28 17:13:44 +03:30
hamed e1740462b5 perf(appointment): batch-fetch pending payments in expiry loop (fix N+1)
AppointmentExpiryService ran one findPendingByAppointment query per expiring
booking. Add PaymentRepository::findPendingByAppointments (one IN query keyed
by appointment id) and use it. Test covers expiry + payment cancellation for
several appointments at once.
2026-06-28 17:10:32 +03:30
hamed 36b87e9817 fix(db): declare onDelete on required FKs that had none
Eight required (nullable:false) FKs had no referential action. Set per the
codebase's existing pattern: CASCADE for owned relations (doctor_insurances,
doctor_secretaries), RESTRICT for owner/reference FKs (Doctor/Clinic.user_id,
invited_by_id, subscription plan/period). Only the two CASCADE FKs need DDL —
RESTRICT is the MySQL default.
2026-06-28 17:04:23 +03:30
hamed 372bea4849 test(orm): regression — every entity with a custom repo declares repositoryClass
Auto-discovers each mapped entity that has a sibling <Entity>Repository class
and asserts getRepository() returns it (not Doctrine's default). Catches the
prod-only opcache.preload bug class that broke /oauth/userinfo.
2026-06-28 16:53:40 +03:30
hamed 093293004a fix(security): enforce ownership on GET date-override (IDOR)
getOverride leaked any doctor's override to any authenticated user; add the
owner-or-admin check (matching the update/delete endpoints) + regression test.
2026-06-28 16:48:41 +03:30
hamed 90736c8149 fix(security): enforce ownership on GET weekly-schedule (IDOR)
getSchedule returned any doctor's schedule to any authenticated user — the
mutation endpoints (update/delete) already checked owner-or-admin but this GET
did not. Add the same check + regression test (fails without the fix).
2026-06-28 16:47:05 +03:30
hamedandClaude Opus 4.8 30c5fbe98c test: functional test infrastructure (ApiTestCase + db_test + JWT helpers)
- .env.test points at db_test (doctrine when@test appends _test suffix)
- ApiTestCase: WebTestCase base with createUser/jwtFor/authJson helpers
- InfraSmokeTest: proves boot + DB + JWT auth pipeline works (/health 200,
  unauth /oauth/userinfo 401, JWT-auth userinfo 200)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:44:05 +03:30
hamedandClaude Opus 4.8 05fde81320 fix(orm): declare repositoryClass on all entities with a custom repository
On prod (opcache.preload + prod container), getRepository(Entity::class)
returned Doctrine's default repository instead of the custom one when the
entity's #[ORM\Entity] had no repositoryClass — so custom finders like
DoctorSecretaryRepository::findAllActiveBySecretary threw BadMethodCallException,
making /oauth/userinfo return 500 after login. Declare repositoryClass explicitly
on all 25 affected entities.

Also add app:create-admin command (create/promote a ROLE_ADMIN user by mobile).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 16:19:07 +03:30
hamed 5bcc97ceb6 feat: add CreateAdminCommand for admin user creation and promotion 2026-06-28 15:57:30 +03:30
hamedandClaude Opus 4.8 5f557b2c57 perf(docker): parallel ext compile (MAKEFLAGS=-j) to beat Coolify build timeout
Coolify builds with --no-cache, so intl (C++) recompiles from source every
deploy (~180s single-threaded) and overran the build timeout (exit 255).
Compile across all cores via MAKEFLAGS=-j$(nproc) to cut it several-fold.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:38:10 +03:30
hamed cb401c6dbf merge: Coolify production hardening (non-root, healthcheck, opcache, graceful shutdown) 2026-06-28 15:28:21 +03:30
hamed 41114aa4cb feat: add comprehensive Docker production hardening documentation for Coolify stack 2026-06-28 15:27:51 +03:30
hamedandClaude Opus 4.8 5150365d2c harden(docker): non-root, dedicated healthcheck, opcache split, graceful shutdown
Coolify-doc-driven production hardening of the deploy stack:
- run the whole stack as non-root www-data; nginx on 8080 (non-privileged),
  pid in /tmp, user directive dropped (Coolify routes to any port)
- docker/healthcheck.sh: hit real /health route via PHP (not just port probe)
- split OPcache config into docker/php/opcache.ini
- graceful shutdown: supervisord stopsignal/stopwaitsecs + worker stop_grace_period
- APCu intentionally not added (Symfony cache uses redis)
- DEPLOY.md: 8080 port, non-root, resource-limit guidance

Verified on linux/amd64: non-root uid=82, /health 200, migrations run,
worker process healthcheck OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:27:34 +03:30
hamed c74b06b3cc fix: correct spelling of 'connexion' to 'connection' in PostgreSQL configuration comments 2026-06-28 14:55:38 +03:30
hamedandClaude Opus 4.8 7af3b5a600 fix: add symfony/redis-messenger + real healthchecks
worker-async crashed in a loop with 'No transport supports Messenger DSN
redis://...' because symfony/redis-messenger was never installed — 10x restart
killed the whole stack. Add the package (v7.4.8).

Also harden healthchecks:
- app: hit the real /health route via PHP get_headers (verifies app boots and
  serves, not just that port 80 is open)
- workers: confirm the messenger:consume process is alive via busybox ps

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:54:45 +03:30
hamedandClaude Opus 4.8 61ffb2ecd8 fix(docker): retry composer install + source fallback for flaky GitHub on Coolify
Coolify build host intermittently gets HTTP 400 from codeload.github.com on
Composer dist downloads. Bump COMPOSER_HTTP_RETRIES, wrap the install in a
5x retry loop, and on final attempt fall back to --prefer-source (git clone,
different endpoint than dist zips).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 14:25:48 +03:30
hamed f1dd540ca2 fix(docker): add coolify network to services for proper connectivity 2026-06-28 13:42:32 +03:30
hamed 745e2fc7f2 fix(docker): adjust formatting for RUN_INIT and define external coolify network 2026-06-28 13:23:07 +03:30
hamed 42bb723333 refactor: separate MariaDB and Redis into independent Coolify resources 2026-06-28 11:28:29 +03:30
hamedandClaude Opus 4.8 471f43248b fix(docker): generate minimal .env when absent (repo .env is gitignored)
Symfony Dotenv::bootEnv() hard-requires a .env file even in prod. The repo's
.env holds dev secrets and is gitignored, so Coolify's clone ships none and the
app fatals with 'Unable to read /app/.env'. Write a minimal APP_ENV=prod .env
at build if one wasn't copied; all real values still come from the compose
environment (clear_env=no), which Dotenv never overwrites.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 10:54:38 +03:30
hamedandClaude Opus 4.8 46e462c6b9 fix(docker): build phpredis from GitHub source (Coolify host can't reach pecl.php.net)
pecl install redis and install-php-extensions both fail on the Coolify build
host with 'No releases available for package pecl.php.net/redis' — that host
cannot reach the pecl registry. Build phpredis 6.1.0 from its GitHub source
tarball instead (github.com is reachable); pdo_mysql/intl/opcache stay as
bundled docker-php-ext-install (no network).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 10:34:18 +03:30
hamed 8ae35b1c8f fix(docker): enhance PHP extension installation with retry logic and use prebuilt binaries 2026-06-28 10:09:32 +03:30
hamed 26fe7c4fc0 fix(docker): implement retry logic for package installation and optimize image usage in docker-compose 2026-06-28 09:57:11 +03:30
hamed 90a908c503 fix(migrations): ensure safe migration steps for doctor_expertise and doctor_cities, drop legacy tables conditionally 2026-06-28 09:32:27 +03:30
hamed e966e11807 feat: update Docker configuration and add deployment documentation for Coolify 2026-06-27 21:27:58 +03:30
hamed 29badf8d68 fix: update references to docker-compose file and remove unused compose files 2026-06-25 21:40:51 +03:30
hamed b56d37f97f feat(docker): add docker-compose configuration for Coolify deployment 2026-06-25 21:40:44 +03:30
hamed cffc88db05 feat: Implement Docker-based deployment for ClinicPro on Coolify
- Added Dockerfile for multi-stage build including PHP, Node.js, and Nginx.
- Created docker-compose.coolify.yaml for service orchestration with app, workers, MariaDB, and Redis.
- Introduced entrypoint.sh for initialization tasks like JWT key generation and database migrations.
- Configured Nginx with default.conf for handling requests and routing to PHP-FPM.
- Added php.ini with production settings and opcache configuration.
- Set up supervisord.conf to manage PHP-FPM and Nginx processes.
- Created frontend-domains.json for managing allowed frontend domains.
- Added gen-cors-env.php script to generate CORS environment variables from frontend domains.
- Updated framework.yaml to configure trusted proxies and headers.
- Created .dockerignore to exclude unnecessary files from the Docker context.
- Added .env.coolify.example for environment variable configuration.
- Documented deployment steps and troubleshooting in coolify.md.
2026-06-25 21:27:28 +03:30
hamed dfda265af4 feat(gitignore): add SQL backup files to .gitignore and remove clinicpromain directory 2026-06-25 21:07:19 +03:30
hamed 174c683813 Remove state data and seed scripts for realistic and test data 2026-06-25 21:07:09 +03:30
hamed 1997b78d11 feat(calendar): add year picker functionality to PersianDatePicker and PersianCalendar 2026-06-25 20:00:13 +03:30
hamed 60b2224ec3 Add AST cache files for representation API and ApiIrService
- Created a new JSON file for the representation API documentation, including nodes and edges that describe the API structure and relationships.
- Added another JSON file for the ApiIrService class, detailing its methods, imports, and relationships with other components.
2026-06-25 19:42:42 +03:30
hamed c2c6ae4d02 feat(migrations): add national_code_verified flag to users and normalize bank_account representation
- Added a new column `national_code_verified` to the `users` table.
- Normalized the `bank_account` field in the `representations` table from a single object to an array of IBANs with a default `verified` status of false.

feat(ApiIrService): implement identity verification client for api.ir

- Created `ApiIrService` to handle identity verification via api.ir.
- Implemented methods for matching national code with mobile and IBAN with national code and birth date.
- Added error handling and logging for external API requests.
2026-06-25 19:38:47 +03:30
hamed 9b608aaeac feat(auth): implement refresh token handling and update login method 2026-06-25 17:37:55 +03:30
hamed 694ee28787 feat(settlement): add receipt handling and detail view for settlements 2026-06-25 17:34:39 +03:30
hamed 71edc772c8 feat(payment): add Jalali date formatting for appointment confirmation SMS 2026-06-25 16:34:54 +03:30
hamed b1a43b7740 feat(sms): add log viewing functionality for SMS messages 2026-06-25 14:20:59 +03:30
hamed 41b435bb88 feat(clinic): allow representation role to view clinic details without edit permissions 2026-06-25 09:42:41 +03:30
hamed e65e506dae feat(representation): add welcome SMS notification for newly added doctors and clinics by representatives 2026-06-25 09:37:20 +03:30
hamed 763ad82b69 feat(settings): add appointment fee configuration and update payment logic 2026-06-24 20:25:58 +03:30
hamed 93fa0fc2c9 feat(representation): enhance finance page with summary cards and improved table styling 2026-06-24 20:16:10 +03:30
hamed 9603b702c1 feat: implement domain guard for commission calculation and enhance representation dashboard
- Added domain guard in CommissionService to ensure commission is calculated only when the appointment is booked under the same representation as the doctor.
- Updated RepresentationController to filter statistics by representation, ensuring accurate data is shown for each representative.
- Introduced new endpoints for the representation dashboard to provide summary statistics, doctor performance, and financial reports.
- Created new pages for RepresentationFinance and RepresentationSettlement to display financial data and allow for settlement requests.
- Added migration to include booking_representation_id in appointments for tracking the representative under which the appointment was booked.
2026-06-24 16:14:41 +03:30
hamed 89e4a424f8 feat(tax): implement tax rate history tracking and API endpoints 2026-06-24 13:17:08 +03:30
hamed 148d033114 feat: Implement financial engine for commission and tax calculations
- Added new configuration keys for appointment and upgrade commissions, tax settings, and SMS panel fee in SiteConfigController and SiteConfigRepository.
- Introduced CommissionService to handle commission calculations for appointments and subscriptions, including tax deductions and SMS fees.
- Created FinancialBreakdown entity and repository to log financial transactions.
- Updated PaymentController to process commissions upon successful payments for appointments and subscriptions.
- Developed FinancialReportPage in the admin panel to display financial breakdowns and summaries.
- Added database migration for the new financial_breakdowns table.
2026-06-24 13:06:17 +03:30
hamed e0abaf5c0c feat(appointment): enforce mandatory patient national code and gender with validation 2026-06-24 12:32:58 +03:30
hamed b7df8cf9ee feat(representation): add endpoints for doctor statistics and toggling doctor status 2026-06-24 12:24:53 +03:30
hamed 8b419d0272 feat(profile): enhance national_code uniqueness error message with masked mobile number 2026-06-24 12:16:16 +03:30
hamed 650e36bce2 feat(profile): enforce uniqueness of national_code across user profiles and update related error handling 2026-06-24 11:52:13 +03:30
hamed 3eb82ffa7d feat(claims): replace PersianDateInput with PersianDatePicker and add quick date range buttons 2026-06-24 06:24:22 +03:30