Skip to content
alimarchalPublic

About

This project is the development of a new, modern website for the Bank of Azad Jammu & Kashmir. The primary goal is to create a user-friendly, secure, and technologically advanced online presence for the bank.

Resources

Stars

2 stars

Watchers

2 watching

Forks

Latest commit

 

History

554 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bank of Azad Jammu & Kashmir (BAJK) - Website & Content Management System

Public website and administration panel for the Bank of Azad Jammu & Kashmir.

  • Public site: products, services, rates, financial reports, branch network, news, careers, downloads, loan calculator, contact form
  • Admin panel: role-based content management, dashboard, contact-message inbox, menu builder, user/role management
  • Public JSON API (v1): branches (with "nearest branch"), regions, districts, contact details, rates, news, careers
  • Urdu documentation (right-to-left): docs/ur/README.md
Backend Laravel 13, PHP 8.3+ (production runs 8.4)
Frontend React 19 + TypeScript, Inertia.js v3, Tailwind CSS v4, Vite 8 (same stack as the official Laravel React starter kit)
Database MariaDB/MySQL (production), SQLite for tests
Auth / permissions Laravel auth + spatie/laravel-permission v8 (permission middleware)
Tests Pest 4 (about 440 tests)

Contents

  1. Quick start (local development)
  2. Configuration (.env)
  3. Creating the first administrator
  4. Everyday commands
  5. Project structure
  6. Roles and permissions
  7. Public API v1
  8. Security
  9. Testing and quality
  10. Deployment to production
  11. Upgrading Laravel
  12. Troubleshooting

Quick start (local development)

Requirements: PHP 8.3 or newer with mbstring, intl, gd, zip and a database driver (pdo_mysql or pdo_sqlite), Composer 2, Node.js 22+ and npm.

git clone https://github.com/alimarchal/Genexis.git
cd Genexis

composer install
npm ci

cp .env.example .env
php artisan key:generate

Pick a database in .env.

SQLite (simplest for development):

DB_CONNECTION=sqlite
DB_DATABASE=/absolute/path/to/Genexis/database/database.sqlite
touch database/database.sqlite

MariaDB / MySQL:

DB_CONNECTION=mariadb
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=bajk
DB_USERNAME=bajk
DB_PASSWORD=choose-a-strong-password

Then create the tables, load the starter content and start everything:

php artisan migrate --seed        # tables + menus, branches, news, products, roles, permissions ...
php artisan storage:link          # makes uploaded files reachable under /storage
php artisan bank:create-admin [email protected] --name="Your Name"   # first administrator (asks for a password)

composer run dev                  # PHP server + queue worker + log viewer + Vite (hot reload)

Open http://localhost:8000 for the website and http://localhost:8000/login for the admin panel.

If you see "Unable to locate file in Vite manifest", run npm run build once, or keep composer run dev running.

Configuration (.env)

.env.example documents every key. The important groups:

Group Keys Notes
Application APP_ENV, APP_DEBUG, APP_URL, APP_KEY Production must use APP_ENV=production, APP_DEBUG=false, an https:// APP_URL.
Database DB_* MariaDB in production.
Sessions SESSION_SECURE_COOKIE, SESSION_ENCRYPT Set SESSION_SECURE_COOKIE=true on https.
Mail MAIL_*, CONTACT_EMAIL, CONTACT_FORM_RECIPIENT Notification e-mail for the contact form. Messages are always saved in the admin inbox even if mail fails.
Bank details CONTACT_PHONE, CONTACT_ADDRESS, BANK_BRANCHES_COUNT, SOCIAL_*, *_URL Shown in header, footer and contact page.
Rates LOAN_RATE_*, DEPOSIT_RATE_*, LOAN_MIN_*, LOAN_MAX_*, LOAN_TENURE_* Loan calculator and rate widgets.
Security TRUSTED_PROXIES, FORCE_HTTPS, CSP_ENABLED, *_RATE_LIMIT See Security.

All bank-specific values are read through config/bank.php. Never call env() outside the config/ folder: after php artisan config:cache it returns null. A test enforces this.

Creating the first administrator

Public registration is disabled. Create administrators from the command line:

php artisan bank:create-admin [email protected] --name="Full Name"

The password must be at least 12 characters with letters and numbers. Running the command again for the same e-mail resets the password. Other staff accounts are created in the panel under User Management -> Users.

Everyday commands

Command What it does
composer run dev Full development stack with hot reload
php artisan test Run the whole test suite
php artisan test --filter=Name Run matching tests
vendor/bin/pint Fix PHP code style
npm run ci Prettier check + ESLint + TypeScript
npm run format / npm run lint Auto-fix JS/TS style
npm run build Production build of the front end
php artisan bank:check Verify that the environment is safe for production
php artisan bank:create-admin <email> Create or reset a super-admin
php artisan news:populate-excerpts Generate missing news excerpts

Project structure

app/
  Console/Commands/        bank:check, bank:create-admin, ...
  Http/Controllers/        public pages, admin CRUD, Admin/ (dashboard, menus, inbox), Api/V1/
  Http/Middleware/         SecurityHeaders, Inertia shared data, menus, breadcrumbs
  Http/Resources/V1/       JSON shapes of the public API
  Models/  Policies/  Services/MenuService.php  Support/PublicFile.php
config/bank.php            every bank-specific setting (contacts, rates, links, security limits)
routes/
  web.php                  public pages + admin panel
  api.php                  /api/v1 public API
resources/js/
  Pages/                   one folder per screen (public and admin)
  components/              shared UI (SafeImage, header, footer, sidebar ...)
public/images/defaults/    built-in fallback illustrations (shown when an image is missing)
scripts/deploy.sh          server-side deployment script
tests/Feature/             Pest tests
docs/                      documentation (Urdu admin guide, API reference)

Where to change things:

  • Website text and lists (news, careers, carousel, branches...): admin panel, no code needed.
  • Navigation menu: admin panel -> System Management -> Admin Menus.
  • Contact details, rates, social/footer links: .env (see above), then php artisan config:cache on the server.
  • A new admin module: controller + Form Requests + React pages + Route::resource(...)->guardedBy('x') in routes/web.php + permissions in RoleAndPermissionSeeder (and a migration that creates them on existing installs, see database/migrations/*contact_submission_permissions.php).

Roles and permissions

Access control follows the spatie/laravel-permission best practices: users have roles, roles have permissions, and the application only ever checks permissions (role names are used for nothing but guard rails).

  • Every role is editable, including super-admin. Permissions are named view|create|edit|delete <module> (for example edit carousels) and are granted or taken away per role under User Management -> Roles -> Edit Permissions. There is no hidden bypass: no Gate::before hook, no role-name check in the UI. A permission removed from a role is really gone for everyone holding that role, and can be added back at any time.
  • Spatie middleware does the enforcing. Admin resources use ->guardedBy('module') (in AppServiceProvider), which maps index/show -> permission:view module, create/store -> permission:create module, edit/update -> permission:edit module, destroy -> permission:delete module. Controllers and one-off routes use the same permission: alias (see bootstrap/app.php for the role, permission and role_or_permission aliases). A user with only view carousels can look but not change anything.
  • No lock-out. The super-admin role must always keep view roles and edit roles so roles stay manageable; it cannot be renamed or deleted, and the last super-admin user cannot be removed or demoted.
  • Seeding never overrides your choices. RoleAndPermissionSeeder only grants permissions it creates for the first time, so re-running it (or a deploy) never restores a permission an administrator removed. When you add a module, add its permissions to the seeder and to a migration (see database/migrations/*upload_permissions.php) that creates them and grants them to super-admin on existing installs.
  • Staff can only hand out permissions they hold themselves; only a super-admin can create or modify another super-admin.
  • Dashboard widgets, sidebar entries and action buttons appear only for permissions the user has (the UI reads auth.permissions).
  • Uploads: view uploads opens the upload log; upload large files raises the size ceiling to UPLOAD_MAX_BYTES.

Public API v1

Read-only, no authentication, JSON, cacheable (Cache-Control: public, max-age=300 + ETag), rate limited per IP (default 60 requests/minute; HTTP 429 with Retry-After).

Base URL: https://<your-domain>/api/v1

Endpoint Description
GET /branches Active branches. Filters: filter[region_id], filter[district_id], filter[type], filter[is_24_hours], filter[name], filter[address]. Sort: sort=name or sort=-code. per_page (max 100).
GET /branches/{code} One branch by branch code, with phones and e-mails.
GET /branches/nearby?lat=&lng=&radius_km=&limit= Branches around a point, nearest first, with distance_km (default radius 25 km, max 200).
GET /regions, GET /districts?region_id= Geography with active branch counts.
GET /contact Head-office phone, e-mail, address, social links.
GET /rates Loan and deposit rates (% p.a.).
GET /news, GET /news/{slug} Published news (full content on the detail endpoint).
GET /careers, GET /careers/{id} Open vacancies.

Example:

curl "https://example.bank/api/v1/branches/nearby?lat=34.37&lng=73.47&radius_km=10&limit=3"
{ "data": [ { "code": "0001", "name": "Main Branch Muzaffarabad", "distance_km": 3.1,
              "phones": ["05822-921181"], "facilities": ["Safe Deposit Boxes"], "...": "..." } ] }

Full reference: docs/api.md. The older GET /api/search?q= endpoint (site search) is unchanged.

Large file uploads

Financial statements, annual reports, highlights, downloads, schedules of charges and vacancy documents can be several GB. A normal form post cannot carry that (PHP, the web server and proxies all cap request bodies), so these forms use a resumable chunked uploader:

  • The browser cuts the file into 4 MB parts, sends them one by one and shows a progress bar with amount, speed and time left. It can pause, resume, cancel, retries automatically after network errors and can continue after a page refresh.
  • Everybody who may manage a module can upload large files for it. Anyone with the module's create/edit permission (financial reports, annual reports, highlights, downloads) may upload up to 10 GB (UPLOAD_LARGE_MAX_BYTES); schedules of charges and vacancy documents up to 500 MB (UPLOAD_DOCUMENT_MAX_BYTES). Users without the permission cannot upload at all, and anyone holding upload large files (super-admin by default) may go up to UPLOAD_MAX_BYTES. No PHP setting needs to be changed: because the file travels in 4 MB parts, PHP's upload_max_filesize / post_max_size never apply.
  • The server keeps a record of every upload (who, which file, size, bytes received, IP, status, SHA-256 of the finished file). Users with view uploads see it under System Management -> Upload Log; running uploads refresh live.
  • A finished file is checked by its real content (not the browser's claim) against the allowed types, moved into storage/app/public, and only attached to a record when the form is saved. A replaced file is deleted only after the new one is saved.
  • Abandoned uploads and finished files that were never attached are removed after 24 hours by php artisan uploads:prune (hourly through the scheduler, see below).
  • Downloads use HTTP Range, so browsers can resume large downloads.

Server requirements:

  1. The web server must accept request bodies of at least the chunk size, e.g. nginx client_max_body_size 16m; (this is not the file size limit). PHP's upload_max_filesize / post_max_size do not need raising.
  2. Free disk space of at least the file size plus the UPLOAD_RESERVE_BYTES reserve (php artisan bank:check warns when the disk is too small). The upload is assembled in storage/app/private/uploads/tmp and then moved, so the file exists once when finished.
  3. The Laravel scheduler must run for automatic clean-up: * * * * * cd /var/www/bajk && php artisan schedule:run >> /dev/null 2>&1.
  4. If a CDN such as Cloudflare sits in front, the 4 MB chunks stay far below its 100 MB request limit.

Security

Built in:

  • Authorization: per-action permissions, privilege-escalation guards, no public registration.
  • Browser protections: nonce-based Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, HSTS on https (app/Http/Middleware/SecurityHeaders.php). Set CSP_ENABLED=false only for debugging.
  • Rate limits per IP: pages 600/min, API 60/min, downloads 30/min, contact form 5 per 10 min (WEB_RATE_LIMIT, API_RATE_LIMIT, DOWNLOAD_RATE_LIMIT, CONTACT_RATE_LIMIT). Behind a reverse proxy (Cloudflare, nginx, cPanel) set TRUSTED_PROXIES (* or a comma-separated list of proxy IPs); otherwise every visitor looks like the proxy and shares one limit.
  • Input safety: Form Requests everywhere, menu links reject javascript:/data: URLs, file downloads only serve active records and never accept paths from the visitor.
  • Privacy: contact-form details are not written to log files.
  • Deployment guard: php artisan bank:check stops a deployment if APP_DEBUG=true in production.

Operational checklist for the server:

  1. APP_ENV=production, APP_DEBUG=false, APP_URL=https://..., SESSION_SECURE_COOKIE=true, LOG_LEVEL=error
  2. A real mail server in MAIL_* (not mailpit)
  3. Database user with access only to the application database
  4. The web server's document root is the public/ folder (never the project root); .env is not web-accessible
  5. Regular database and storage/app/public backups
  6. Keep dependencies current (Dependabot pull requests, composer audit)

Report security problems privately to the repository owner.

Testing and quality

php artisan test                 # PHP tests (SQLite in memory)
vendor/bin/pint --test           # style check
npm run ci                       # prettier + eslint + tsc

The tests are behavioural: for example they verify that a view-only user cannot delete, that inactive documents cannot be downloaded, that hidden news is not shown, that distances in the nearby-branches API are correct and that every route('...') used by the React code exists on the server. GitHub Actions runs everything on PHP 8.3 and 8.4 for every pull request (.github/workflows/ci.yml).

Deployment to production

Production assumptions: Linux, PHP 8.3+ (8.4 recommended), MariaDB 10.6+/11, a web server whose document root is public/, Composer on the server, SSH access.

One-time setup on the server

git clone https://github.com/alimarchal/Genexis.git /var/www/bajk
cd /var/www/bajk
cp .env.example .env            # then edit: APP_ENV, APP_DEBUG=false, APP_URL, DB_*, MAIL_*, TRUSTED_PROXIES ...
composer install --no-dev --optimize-autoloader
php artisan key:generate
php artisan migrate --force --seed
php artisan storage:link
php artisan bank:create-admin [email protected]
php artisan bank:check          # must report no errors
php artisan config:cache && php artisan route:cache && php artisan view:cache && php artisan event:cache

Make storage/ and bootstrap/cache/ writable by the web-server user. Build assets once (npm ci && npm run build) or upload public/build/ from CI.

An existing production database that already matches the migrations needs only php artisan migrate --force (new migrations add permissions only).

Automatic deployment (GitHub Actions)

.github/workflows/deploy.yml runs after CI passes on main. Add these repository secrets (Settings -> Secrets and variables -> Actions); without them the workflow skips itself:

Secret Example
DEPLOY_HOST server.example.com
DEPLOY_USER deploy
DEPLOY_PATH /var/www/bajk
DEPLOY_SSH_KEY private key of a deploy-only user
DEPLOY_PORT 22 (optional)

On each push to main the workflow builds the assets, uploads public/build, then runs scripts/deploy.sh on the server: pull (fast-forward only), composer install --no-dev, php artisan bank:check, maintenance mode, migrate --force, caches, queue restart, back online. If bank:check finds an error nothing on the live site is touched.

Manual deployment is the same command on the server: bash scripts/deploy.sh.

Rollback

git log --oneline -5                     # choose the previous good commit
git checkout <commit> && composer install --no-dev -o
php artisan config:cache && php artisan route:cache && php artisan view:cache

Migrations are additive; restore the database backup only if a migration itself is at fault.

Upgrading Laravel

The code follows framework conventions (no framework-internal hacks, bootstrap/app.php configuration, Form Requests, resources, config-only env()), so major upgrades should be small:

  1. Create a branch and read the official upgrade guide for the target version.
  2. Raise laravel/framework in composer.json, then composer update -W. Dependabot already proposes minor updates weekly.
  3. Packages that usually need a matching bump: inertiajs/inertia-laravel, spatie/*, pestphp/*, laravel/boost, laravel/tinker.
  4. Run php artisan test, npm run ci and open the main pages (CI does the first two automatically).
  5. composer.json pins config.platform.php to 8.3.0 so the lock file installs on 8.3 and 8.4; raise it when you drop 8.3.

Troubleshooting

Symptom Cause / fix
White page / "Unable to locate file in Vite manifest" Assets not built: npm run build
Images missing (default illustration shown) File not uploaded or php artisan storage:link not run
Values from .env ignored after editing Config is cached: php artisan config:cache (or config:clear in development)
Contact form works but no e-mail arrives Check MAIL_*; messages are still in Admin -> Contact Messages
Everyone gets "Too many requests" Behind a proxy without TRUSTED_PROXIES; set it
403 in the admin panel The user's role lacks that module's permission (User Management -> Roles)
bank:check fails with APP_DEBUG Set APP_DEBUG=false in the production .env
Session cookie error "name cannot be empty" Remove an empty SESSION_COOKIE= line from .env

License

Proprietary - Bank of Azad Jammu & Kashmir. All rights reserved.

About

This project is the development of a new, modern website for the Bank of Azad Jammu & Kashmir. The primary goal is to create a user-friendly, secure, and technologically advanced online presence for the bank.

Resources

Stars

2 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages