* docs: add CLAUDE.md for Claude Code guidance * fix: handle missing settings table in installation middlewares RedirectIfInstalled crashed with "no such table: settings" when the database_created marker file existed but the database was empty. Changed to use isDbCreated() which verifies actual tables, and added try-catch around Setting queries in both middlewares. * feat: pre-select database driver from env in installation wizard The database step now reads DB_CONNECTION from the environment and pre-selects the matching driver on load, including correct defaults for hostname and port. * feat: pre-select mail driver and config from env in installation wizard The email step now fetches the current mail configuration on load instead of hardcoding the driver to 'mail'. SMTP fields fall back to Laravel config values from the environment. * refactor: remove file-based DB marker in favor of direct DB checks The database_created marker file was a second source of truth that could drift out of sync with the actual database. InstallUtils now checks the database directly via Schema::hasTable which is cached per-request and handles all error cases gracefully.
3.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
InvoiceShelf is an open-source invoicing and expense tracking application built with Laravel 13 (PHP 8.4) and Vue 3. It supports multi-company tenancy, customer portals, recurring invoices, and PDF generation.
Common Commands
Development
composer run dev # Starts PHP server, queue listener, log tail, and Vite dev server concurrently
npm run dev # Vite dev server only
npm run build # Production frontend build
Testing
php artisan test --compact # Run all tests
php artisan test --compact --filter=testName # Run specific test
./vendor/bin/pest --stop-on-failure # Run via Pest directly
make test # Makefile shortcut
Tests use SQLite in-memory DB, configured in phpunit.xml. Tests seed via DatabaseSeeder + DemoSeeder in beforeEach. Authenticate with Sanctum::actingAs() and set the company header.
Code Style
vendor/bin/pint --dirty --format agent # Fix style on modified PHP files
vendor/bin/pint --test # Check style without fixing (CI uses this)
Artisan Generators
Always use php artisan make:* with --no-interaction to create new files (models, controllers, migrations, tests, etc.).
Architecture
Multi-Tenancy
Every major model has a company_id foreign key. The CompanyMiddleware sets the active company from the company request header. Bouncer authorization is scoped to the company level via DefaultScope (app/Bouncer/Scopes/DefaultScope.php).
Authentication
Three guards: web (session), api (Sanctum tokens for /api/v1/), customer (session for customer portal). API routes use auth:sanctum middleware; customer portal uses auth:customer.
Routing
- API: All endpoints under
/api/v1/inroutes/api.php, grouped withauth:sanctum,company, andbouncermiddleware - Web:
routes/web.phpserves PDF endpoints, auth pages, and catch-all SPA routes (/admin/{vue?},/{company:slug}/customer/{vue?})
Frontend
- Entry point:
resources/scripts/main.js - Vue Router:
resources/scripts/admin/admin-router.js(admin),resources/scripts/customer/customer-router.js(customer portal) - State: Pinia stores in
resources/scripts/admin/stores/ - Path aliases:
@=resources/,$fonts,$imagesfor static assets - Vite dev server expects
invoiceshelf.testhostname
Backend Patterns
- Authorization: Silber/Bouncer with policies in
app/Policies/. Controllers use$this->authorize(). - Validation: Form Request classes, never inline validation
- API responses: Eloquent API Resources in
app/Http/Resources/ - PDF generation: DomPDF (
GeneratesPdfTrait) or Gotenberg - Email: Mailable classes with
EmailLogtracking - File storage: Spatie MediaLibrary, supports local/S3/Dropbox
- Serial numbers:
SerialNumberFormatterservice - Company settings:
CompanySettingmodel (key-value per company)
Database
Supports MySQL, PostgreSQL, and SQLite. Prefer Eloquent over raw queries. Use Model::query() instead of DB::. Use eager loading to prevent N+1 queries.
Code Conventions
- PHP: snake_case, constructor property promotion, explicit return types, PHPDoc blocks over inline comments
- JS: camelCase
- Always check sibling files for patterns before creating new ones
- Use
config()helper, neverenv()outside config files - Every change must have tests (feature tests preferred over unit tests)
- Run
vendor/bin/pint --dirty --format agentafter modifying PHP files
CI Pipeline
GitHub Actions (check.yaml): runs Pint style check, then builds frontend and runs Pest tests on PHP 8.4.