Skip to content

Latest commit

 

History

History
274 lines (208 loc) · 11.5 KB

File metadata and controls

274 lines (208 loc) · 11.5 KB

Setup & Usage

Prerequisites

  • Web server: Apache 2.x with mod_rewrite enabled
  • PHP: 8.x with mysqli, gd (image manipulation), and openssl extensions
  • Database: MariaDB 10.x or MySQL 8.x — empty database recommended
  • Disk: ~100MB free for application + product images

Installation

Option 1: Automated (recommended)

bash install.sh

The script:

  1. Detects PHP, MySQL/MariaDB, and Apache
  2. Probes for admin DB access — prefers sudo mysql (unix_socket auth on Debian/Ubuntu MariaDB), falls back to user/password prompt
  3. Creates the database from schema.sql
  4. Creates a least-privilege application user webuser@localhost with only SELECT/INSERT/UPDATE/DELETE on the inventory database
  5. Generates .env with a secure APP_SECRET (32 random bytes hex)
  6. Adds execute (o+x) on the home directory if needed so Apache can traverse a symlinked DocumentRoot
  7. Creates /var/www/html/inventory → project-root symlink
  8. Adds Listen 8080 to Apache ports.conf if missing
  9. Writes Apache vhost config at /etc/apache2/sites-available/inventory.conf listening on port 8080
  10. Enables the site (a2ensite) and reloads Apache after configtest

Access the app at http://<server>:8080 (or http://localhost:8080 from the host itself).

Reinstall (destructive)

To wipe the database, vhost, symlink, uploaded user content, and .env, then reinstall fresh:

bash install.sh --reinstall      # prompts: type 'RESET' to confirm
bash install.sh --reinstall -y   # non-interactive

The reinstall step preserves git-tracked files in uploads/ (default placeholder images) — only user-uploaded content is removed. The current .env is backed up as .env.bak.<timestamp> before deletion.

Option 2: Manual

  1. Clone the repo somewhere — typically /var/www/html/inventory, or symlinked there from your dev tree.

  2. Create the database:

    sudo mysql -e "CREATE DATABASE inventory CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
    sudo mysql inventory < schema.sql
  3. Create an app user:

    sudo mysql -e "CREATE USER 'webuser'@'localhost' IDENTIFIED BY '';
                   GRANT SELECT, INSERT, UPDATE, DELETE ON inventory.* TO 'webuser'@'localhost';
                   FLUSH PRIVILEGES;"
  4. Write .env (project root):

    DB_HOST=localhost
    DB_USER=webuser
    DB_PASS=
    DB_NAME=inventory
    APP_SECRET=<run: openssl rand -hex 32>
    SESSION_TIMEOUT_MINUTES=30
    

    SESSION_TIMEOUT_MINUTES controls the idle-session timeout (default 30; set higher for dev, 0 to disable).

  5. Permissions on uploads/:

    sudo chmod -R 775 uploads/
    sudo chown -R www-data:www-data uploads/
  6. Apache vhost (/etc/apache2/sites-available/inventory.conf):

    <VirtualHost *:8080>
        ServerName inventory.local
        DocumentRoot /var/www/html/inventory
        <Directory /var/www/html/inventory>
            AllowOverride All
            Require all granted
            DirectoryIndex index.php
        </Directory>
        <FilesMatch "\.(env|sql|sh|md|gitignore)$">
            Require all denied
        </FilesMatch>
        ErrorLog ${APACHE_LOG_DIR}/inventory_error.log
    </VirtualHost>

    Then:

    echo "Listen 8080" | sudo tee -a /etc/apache2/ports.conf
    sudo a2ensite inventory.conf
    sudo apache2ctl configtest && sudo systemctl reload apache2
  7. If the DocumentRoot is a symlink into a home directory, give Apache traversal access:

    chmod o+x ~

Applying Migrations

After pulling new code, apply any pending schema migrations against the live DB. The runner uses .env credentials, executes each .up.sql once, and is idempotent:

php scripts/migrate.php             # apply all pending
php scripts/migrate.php --status    # show applied / pending
php scripts/migrate.php 022 023 024 # apply a specific list

install.sh already applies the full schema, so migrations only matter when upgrading an existing deployment. The migration runner is shipped via PR #53.

Running Tests

Prerequisites

  • .env must exist with valid DB credentials (the test harness connects to the live DB using a HARNESS_ data prefix)
  • Composer dev dependencies installed: composer install
  • Apache must be running (SecurityHeadersTest makes real HTTP requests to localhost:8080)

PHP test suites

bash tests/run.sh           # all suites (PHP + Playwright)

Expected output when app is reachable at http://blueberry.local:8080 (test vhost):

OK--- Authentication (integration) ---
OK: All tenancy tests passed.
OK: All org management tests passed.
OK--- Security Headers (http) ---
=========================================
 Summary: 7/7 legacy suites passed
=========================================

The legacy harness (run.sh) covers: Auth, CRUD, Settings, SoftDelete, Tenancy, OrgManagement, SecurityHeaders.

PR #52 introduced PHPUnit-style tests for the new security primitives. Run them with:

vendor/bin/phpunit

PHPUnit suites cover: CSRF, Session (idle timeout + is_secure_context()), PasswordReset (token lifecycle), Permissions (can() + role defaults), plus infra smoke tests (Health, Backup, LogRotate, InfraSmoke).

Playwright UI tests

Requires a running app and seeded auth state:

npx playwright test                    # headless
npx playwright test --headed           # with browser visible
npx playwright test tests/ui/products  # specific spec

Playwright tests seed their own HARNESS_ users via auth.setup.js. If the setup spec fails, the remaining UI tests won't run — check that the app is accessible at http://localhost:8080.

Default Accounts

All passwords are bcrypt-hashed in the database (and auto-upgraded from legacy SHA1 on first login).

Role Username Password user_level
Administrator admin admin 1
Supervisor special special 2
Default User user user 3

Change all three on first login via Settings → Change Password.

Daily Workflows

Administrator (level 1)

  • Manage users and groups (Users → Add/Edit)
  • Manage products, categories, stock adjustments
  • Process sales, generate invoices and picklists
  • Run sales and stock reports
  • Manage customers (contact, payment info)

Supervisor (level 2)

  • Add/edit products and adjust stock
  • Create orders and sales, print invoices and picklists
  • View customer details
  • Access sales and stock reports

Default User (level 3)

  • Browse products by category, search by name/SKU
  • Add sales to existing orders
  • Look up order status

Common Tasks

Adding a product

  1. Products → Add Product
  2. Fill in: Name, SKU, Location, Quantity, Buy Price, Sale Price, Category
  3. Optionally upload an image (PNG/JPG/GIF; handled by the Media class)
  4. Submit — created via prepared INSERT

Creating a sales order

  1. Sales → Add Order
  2. Search or select a customer (AJAX autocomplete from includes/sql.php)
  3. Add line items by SKU or product search
  4. Each sale decreases product quantity via decrease_product_qty()
  5. Print invoice or picklist from the order detail page

Running a sales report

  • Date range: Reports → Sales Report → pick start/end → submit (find_sale_by_dates())
  • Daily: Reports → Daily Sales → pick year/month
  • Monthly: Reports → Monthly Sales → pick year

Managing user access

  1. Users → Groups to define tiers; each group has name, level (1-3), status
  2. Users get a user_level matching a group
  3. Disabling a user (status=0) or group (group_status=0) blocks access via page_require_level()

Per-module permissions (Admin)

Users → Permissions opens a matrix of (user × module × action)view, create, edit, delete. Admins always bypass; for Supervisors and Users the matrix overrides the role defaults (Supervisor = view/create/edit, User = view only). Used by can() / require_permission() in module handlers. Backed by the permissions table (migration 024).

Audit log (Admin)

Users → Audit Log lists CRUD + login/logout events from the audit_log table (user, module, action, record id, summary, IP). The recorder (audit() in includes/sql.php) is wrapped in try/catch so a DB outage cannot white-screen a login. Migration 023 created the table.

Password reset (user-facing)

From the login page, Forgot password? captures a username or email and creates a single-use, 1-hour reset token (bcrypt-hashed in the password_resets table). The reset link is currently surfaced via server logs only — SMTP email delivery is deferred to a follow-up plan. Visiting the link loads users/reset_password.php, which validates the token and atomically updates the password + marks the token consumed. Migration 022 created the table.

Demo Data

To seed the Default Organization with realistic demo data (products, customers, orders, sales, stock):

php scripts/demo_seed.php

This inserts:

Section Count Notes
Categories 6 Microcontrollers, Single Board Computers, Plants & Living Goods, Cables & Connectors, Sensors & Modules, Tools & Equipment
Products 12 3 with real images (Arduino Nano, RPi 3, Pothos); rest use the placeholder
Customers 8 Ontario community orgs with addresses, emails, and payment methods
Orders 6 Mixed statuses: fulfilled ×3, shipped ×1, processing ×1, pending ×1
Sales 14 Line items across 5 of the 6 orders
Stock 12 One opening-receipt movement per product

The script is idempotent — it skips silently if demo data is already present. To remove the demo data and re-seed from scratch:

php scripts/demo_seed.php --clean

The --clean step deletes only rows matching the known demo SKUs and customer names — it will not touch any data you have entered manually.

Note: The seeder reads .env the same way the app does. It requires the database to be set up (run install.sh or follow the manual install steps first).

Troubleshooting

Symptom Likely cause Fix
Browser shows 403 Forbidden on localhost:8080 Apache can't traverse home dir to symlinked DocumentRoot chmod o+x ~ (execute-only, no list)
"Database connection failed. Please try again later." .env has wrong creds, or webuser is missing Re-check .env; recreate webuser per manual install step 3
"Failed to select database" Database doesn't exist sudo mysql inventory < schema.sql
White screen / no output PHP fatal error sudo tail -50 /var/log/apache2/inventory_error.log
Login form accepts credentials but redirects right back to login Old bug — header role check used string compare on int. Fixed in 2026-05-14 cycle. If reappears, check layouts/header.php:74-82 Cast to int: (int)$user['user_level'] === 1
CSRF "Invalid security token" on login Stale tab / expired session Refresh login page to fetch a new token
Upload failures uploads/ not writable by www-data sudo chown -R www-data:www-data uploads/
Product image shows broken Missing no_image.jpg placeholder git checkout HEAD -- uploads/ to restore tracked seeds
AJAX search not working jQuery not loaded Open browser console; look for 404s on libs/js/jquery.min.js
Apache reload silently skipped during install Pre-2026-05-14 install.sh checked stdout but apache2ctl configtest writes to stderr Update to current install.sh (2>&1 before grep)