Unreleased
Added
  • In-app notifications — bell widget + notifications page, driven by a live feed from the notification microservice migration-service notification api_gateway ui_gateway admin_ui admin_backend profile_microservice account_microservice finance_microservice helpcenter
    Tenant DB migration required. Run php artisan tenant:migrate {slug} for every active tenant before this release goes live. Adds read_at column + composite index on the notifications table. Without it the user feed endpoints fail with SQL errors. Template seeder required. Run php artisan tenant:seed {slug} --class=InAppTemplateSeeder for every tenant. Seeds the single generic_in_app template. Without it, the in-app channel silent-skips every send (nothing appears in the bell). Env vars required on newly-scaffolded services. account_microservice and helpcenter now talk to the notification microservice. Set NOTIFICATION_SERVICE_URL, NOTIFICATION_SERVICE_TIMEOUT, NOTIFICATION_SERVICE_ENABLED, and INTERNAL_API_KEY on both servers. INTERNAL_API_KEY must match the value on the notification microservice. Deployment order: migration-service → notification → api_gateway → ui_gateway + admin_ui → admin_backend + profile_microservice + account_microservice + finance_microservice + helpcenter. Cache clears: after deploy, run php artisan config:clear && php artisan route:clear on every service touched, plus php artisan view:clear on ui_gateway + admin_ui.
    • Single shared `generic_in_app` template renders every in-app notification; per-event copy (title, body, deep-link) is supplied by the call site. Collapses template authoring from 64 slugs to 1.
    • `NotificationDispatcher::send()` refactored to accept `channels: ['in_app', 'email']` and fan out per channel. A channel with no active template is skipped silently so the other channel still fires — this keeps email and in-app tracks independent.
    • Back-compat shim: legacy single-channel `channel: 'email'` payloads still work (the live `bank_account_deleted` integration and future RBS reactive actions keep running without coordinated redeploy).
    • New migration `2026_04_23_120001_add_read_at_to_notifications` — adds `read_at` nullable timestamp + composite index `(recipient_id, channel, read_at)` for the three hot feed queries (unread count, list, mark-all-read)
    • `InAppTemplateSeeder` seeds `generic_in_app` with `title`/`body`/`link` variables; registered in `DatabaseSeeder` so every new tenant gets it automatically
    • `InAppChannel` added to the notification microservice (no external delivery — the row's presence in the tenant DB IS the delivery; queue worker marks `status=sent` for admin-log consistency)
    • `RejectReadonlyToken` middleware + `readonly.token` alias — blocks admin impersonation tokens from POSTing to mark-read endpoints so the user's true unread state is preserved
    • Four new user-facing endpoints on the notification microservice under `auth:sanctum` + `readonly.token`: `GET /user/notifications`, `GET /user/notifications/unread-count`, `POST /user/notifications/{id}/read`, `POST /user/notifications/read-all`
    • Matching proxy endpoints on api_gateway under `/api/{tenant}/user/notifications/*` — forwards the user's bearer token + X-Tenant header (no X-Internal-Key — authenticates as the real Sanctum user on the downstream service)
    • Desktop header gains a live bell dropdown with unread-count badge and a "Mark all as read" button; mobile avatar menu gets a badged bell link
    • Polling runs every 60s via `public/assets/custom_js/notifications-bell.js`, pauses when the tab is hidden (`document.visibilityState`) and resumes on visibility change — zero network chatter for inactive tabs
    • `/notifications` page rewritten as a paginated list with All / Unread filters and per-row mark-as-read, driven by `public/assets/custom_js/notifications-page.js`
    • admin_ui template editor adds `In-App` to the channel dropdown; admin notification log gains an `In-App` channel filter and renders `in_app` as "In-App" rather than "In_app"
    • `admin_backend` (40 events): KYC approved/rejected, document status changed, document remarks, admin uploads (identity/address/networth), admin-verify-email, profile-updated-by-admin, withdrawal approve/reject/comment, deposit approve/reject/comment, internal transfer approve/reject/comment, admin password reset, ticket replied, ticket created by admin, account block/unblock/deactivate, wallet credit/debit (Clients + Accounts views), internal transfer by admin, admin-opened live/demo accounts, MT5 leverage update, MT5 password change by admin, MT5 manual deposit/withdrawal, MT5 account delete/transfer/disable, MT5 account request approve/reject
    • `profile_microservice` (12 events): password changed, profile updated (self), document uploads (identity, address, networth, signature), bank account added/updated/removed, crypto address added/updated/removed
    • `finance_microservice` (8 events): withdrawal OTP, withdrawal submitted (bank + crypto), deposit submitted, deposit receipt uploaded, deposit received (PSP webhook), deposit expired (auto-cancel after 4h), internal transfer submitted
    • `account_microservice` (3 events, new scaffold): live account created (self-service), demo account created (self-service), MT5 password changed (self)
    • `helpcenter` (1 event, new scaffold): support ticket created
    • ~15 user-facing events skipped as Phase 3 — mostly auth signup/password-reset flows where the user isn't logged in yet, promotion broadcasts (deferred to P4 in the email track), and admin-initiated soft-delete (a deleted user cannot see their bell). Full rationale in `documents/IN_APP_NOTIFICATIONS_PLAN.md` §9.
    • RBS `notify_user_inapp` reactive action — deferred until the RBS microservice goes live. Adding it later is a ~15-line follow-up (one entry in `rbs/config/catalog.php`, one case in `api_gateway/app/Services/RbsActionExecutor.php`).
    • Admin-side in-app bell — tracked as Milestone E. The backend infrastructure supports it (schema has `recipient_type='admin'`), but the admin-side UI + endpoints are a separate project.
    • Every call site wraps the notification dispatch in try/catch; notification-service failures never block the business action they accompany (KYC decision, withdrawal approval, deposit credit, etc.)
    • Existing direct `Mail::to()` email paths are untouched — in-app fires alongside email via `channels: ['in_app']`. When each per-event email template is authored on migration-service, its call site will switch to `channels: ['in_app', 'email']` and retire the direct-mail block in the same PR. This avoids double-sending emails during the transition.
    • Readonly (admin impersonation) tokens can list / count notifications but cannot mark anything read — preserves the real user's unread state when an admin is viewing their account.
  • NotificationSettingSeeder for encrypted SendGrid configuration migration-service
    REQUIRED on every tenant before this release goes live. Must be run against every active tenant DB (dev, demo, zyz, okm, and any new tenants added later). Without this, the notification microservice has no SendGrid key to read and every email send will fail, breaking all flows that depend on email.
    • Seeds `sendgrid_api_key`, `from_email`, `from_name` rows in the tenant `notification_settings` table
    • Values read from `SEED_SENDGRID_API_KEY`, `SEED_FROM_EMAIL`, `SEED_FROM_NAME` env vars (not committed to git)
    • `sendgrid_api_key` is encrypted at rest with Laravel `Crypt` (APP_KEY, shared across internal microservices)
    • Safe to re-run: skips any key whose row already has a non-empty value, and skips any key whose seed env var is empty
    • Registered in `DatabaseSeeder`; run per-tenant via `php artisan tenant:seed {slug} --class=NotificationSettingSeeder`
  • Encrypted-at-rest handling for sendgrid_api_key in NotificationSetting model notification
    • `NotificationSetting::get()` / `::set()` transparently decrypt/encrypt values for keys listed in `$encryptedKeys`
    • `EmailChannel` unchanged: continues to call `NotificationSetting::get('sendgrid_api_key')` and receives plaintext
    • Decryption failures log a warning and return the default instead of crashing the send path
  • Template versioning for the notification microservice migration-service notification
    REQUIRED to run tenant migrations on every tenant before this release goes live. Five new migrations (timestamps 2026_04_20_120001-120005) add the notification_template_versions table, current_version_id on templates, template_version_id + raw_body on notifications, backfill existing data, and drop the legacy body_preview column. Run for every tenant (dev, demo, zyz, okm, and any future tenants): php artisan tenant:migrate {slug}. Until these run, the notification microservice will reject new sends because it expects current_version_id to be set on every active template. Deployment order: deploy migration-service → run migrations → deploy the notification microservice → restart queue workers. Deploying the new notification code before the migrations run will cause inserts to fail because the new columns don't exist yet.
    • Every notification now pins a specific `template_version_id` at dispatch time, so past emails can be re-rendered exactly as delivered even if the template is edited later
    • Template edits create a new immutable version row with SHA-256 content-hash dedup (identical re-saves don't spam new versions)
    • Rendered email body is no longer persisted for template-based sends — re-rendered from the pinned version at send time by `ProcessNotification`, cutting notifications-table storage from a few KB/row to a few hundred bytes/row
    • Subject is still rendered and stored at dispatch time for fast admin log display
    • Raw sends (`/api/send-raw`) continue to store the full body inline in a new `raw_body` column since there is no template to re-render from. See `documents/TECH_DEBT.md` for the planned retirement of this path
    • Admin CRUD API contract unchanged — no admin_ui or api_gateway code changes required for this release
    • Channel handlers now receive a `RenderedMessage` DTO instead of reading a body column from the notification model; `EmailChannel` updated accordingly, any future channel must follow the same contract
    • `NotificationTemplate::saveVersion()` helper called on every template create/update by `AdminTemplateController`
  • First notification microservice integration — bank_account_deleted email migration-service profile_microservice
    Requires the template seed to be re-run on every tenant. Run php artisan tenant:seed {slug} --class=NotificationTemplateSeeder for dev, demo, zyz, okm. The existing four templates are deduped via SHA-256 hash; only the new bank_account_deleted row is inserted.
    • New `bank_account_deleted` template in `NotificationTemplateSeeder` (migration-service). Modern Metronic-style HTML, green success header, masked account number (last 4 only), amber "Didn't do this?" security callout, CTA to `/profile-step`
    • `profile_microservice` gains a `NotificationService` client (scaffold) and `notification_service` + `internal_api_key` blocks in `config/services.php` — ready for future profile notifications without further plumbing
    • `ProfileController::deleteUserBankAccount` captures bank name, account number, beneficiary, and timestamp before delete, then fires the notification via a best-effort helper (failures logged, do not affect the delete response)
    • Company branding (`{{company.name}}`, `{{company.support_email}}`, `{{company.website_url}}`) resolved from tenant settings at dispatch time; user-facing CTA URL built from `tenants.frontend_url`
    • **New env vars required on profile_microservice:** `NOTIFICATION_SERVICE_URL`, `NOTIFICATION_SERVICE_TIMEOUT`, `NOTIFICATION_SERVICE_ENABLED`, `INTERNAL_API_KEY`. Must share `INTERNAL_API_KEY` with the notification microservice
v1.1.2.0 2026-04-17
Changed
  • Email templates now use dynamic tenant company branding instead of hardcoded values admin_backend auth_microservice account_microservice finance_microservice profile_microservice helpcenter
  • Legacy templates: replaced config('app.name') and inline Setting::where() queries with $companyName passed from controller via Mailable
  • New (OKM) templates: replaced all hardcoded Oktrum branding — company name, logo, website, contact email, phone, addresses — with dynamic profile.company.* tenant settings
  • Added companyProfile() bulk-fetch method to EmailTemplateFlag on admin_backend
  • Each microservice Mailable now resolves the full company profile in one query
Added
  • New settings seeder CompanyProfileSettingSeeder with 5 keys: profile.company.logo_url, profile.company.website_url, profile.company.phone, profile.company.address_physical, profile.company.address_registered migration-service
  • Seeder rules in database-conventions.md SOP — use firstOrCreate for tenant-customisable settings, never modify existing seeders api_gateway
  • Updated versioning SOP to 4-segment format: MAJOR.FEATURE.IMPROVEMENT.FIX api_gateway
v1.1.1 2026-04-09
Changed
  • Hide manual "Add Payment Method" option and the model code field from the admin UI admin_ui
Fixed
  • Compliance redirect now sends users to the Upload Documents page when their profile is complete but documents were rejected, instead of always redirecting to Profile ui_gateway
v1.1.0 2026-04-08
Added
  • Permanent (hard) delete option for clients admin_ui api_gateway admin_backend
    • Toggle switch in soft-delete confirmation modal
    • "Permanently Delete" action for already soft-deleted clients
    • "Permanently Delete Selected" bulk action
    • Deletes all related data: profile, accounts, transactions, invoices, wallets, documents, bank accounts, tickets
  • Admin "Access Client" readonly impersonation migration-service auth_microservice api_gateway ui_gateway admin_ui finance_microservice account_microservice profile_microservice helpcenter
    • 15-minute Sanctum token with `readonly` ability
    • `RejectReadonlyToken` middleware on all microservices blocks writes from readonly tokens
    • `ReadonlyGuard` middleware on ui_gateway blocks non-GET requests in readonly session
    • Red readonly banner and global JS to hide submit buttons
    • `frontend_url` column on `tenants` table for multi-tenant dashboard URL resolution
  • New admin setting to restrict Live accounts for unapproved users: when enabled, users who have not completed compliance can still access the Accounts page but can only view and create Demo accounts. The Live account section is hidden until compliance is approved.
Fixed
  • API Gateway client delete proxy now forwards request body to downstream service api_gateway
  • Fixed deposit table detail popup not closing automatically after a successful action
v1.0.0 2026-03-25
Released
  • Initial production deployment — OKM tenant live on servers.com Bahrain
  • 11 microservices + 3 queue workers deployed via Docker Compose
  • MariaDB 10.6 on app server, host Redis, Nginx → Apache → Docker proxy chain
  • SSL via Let's Encrypt on all 3 public domains (my.oktrum.com, bo.oktrum.com, okm.api.ataria.pro)
  • Gateway DB (ataria_gateway) + tenant DB (okm) with unified APP_KEY
  • Shared documents volume, iptables firewall rules, localhost-only port bindings