diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..f3cf7ab --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,203 @@ +# LocalWeb Development Environment Guide + +## Overview + +This document describes the LocalWeb Collections PHP MVC application development environment setup using LXC containers. + +## Architecture + +### Host Environment +- **Location**: `/home/edwin/Documents/Projects/localweb/` +- **User**: `edwin` +- **Container Runtime**: Incus/LXC +- **Container Name**: `www` + +### LXC Container `www` +- **OS**: Linux distribution with Apache2 + PHP +- **Web Root**: `/var/www/localhost/` (mounted from host project directory) +- **Service**: `apache2.service` (running as systemd service) +- **Database**: SQLite (`collections.sqlite`) +- **PHP Version**: Compatible with Composer dependencies + +### Network Setup +- **LXC Proxy**: Container port 80 → Host port 80 +- **Access**: `http://localhost` on host serves the webapp +- **Internal**: Apache binds to `127.0.0.1:80` inside container + +## Project Structure + +``` +/home/edwin/Documents/Projects/localweb/ +├── src/ # PHP MVC source code +│ ├── Controllers/ # Route handlers +│ ├── Models/ # Data models +│ └── Services/ # Business logic +├── templates/ # Twig templates +├── public/ # Web root (entry point) +├── vendor/ # Composer dependencies +├── collections.sqlite # SQLite database +└── config.php # Configuration +``` + +## Development Workflow + +### 1. Local Development +- Edit files in `/home/edwin/Documents/Projects/localweb/` +- Changes are immediately reflected in the container via shared mount +- Test via `curl localhost` or browser at `http://localhost` + +### 2. Container Management +```bash +# Execute commands in container +incus exec www -- + +# Check Apache status +incus exec www -- systemctl status apache2 + +# View Apache logs +incus exec www -- tail -f /var/log/apache2/error.log + +# Reload Apache +incus exec www -- systemctl reload apache2 + +# Check PHP version +incus exec www -- php -v +``` + +### 3. Database Operations +```bash +# Access database in container +incus exec www -- sqlite3 /var/www/localhost/collections.sqlite + +# Check database permissions +incus exec www -- ls -la /var/www/localhost/collections.sqlite +``` + +## Application Details + +### Technology Stack +- **PHP**: MVC application with PSR-4 autoloading +- **Framework**: Custom MVC with FastRoute routing +- **Templating**: Twig templates +- **Database**: SQLite with PDO +- **Frontend**: Bootstrap 5 + vanilla JavaScript (SPA-like) +- **Dependencies**: Managed via Composer + +### Key Features +- **Collections Management**: CRUD operations for items +- **Categories**: Hierarchical category structure +- **QR Codes**: Automatic generation for items +- **Multi-language**: Dutch/English support +- **Search & Filter**: Dynamic content loading + +### Routing +- **Web Routes**: Full page loads (`/`, `/categories`, `/parts`) +- **API Routes**: AJAX endpoints (`/api/items`, `/api/categories`) +- **Entry Point**: `public/index.php` + +## Development Commands + +### Dependencies +```bash +# Install/update dependencies +composer install +composer update + +# Check for security issues +composer audit +``` + +### Testing +```bash +# Manual testing via curl +curl localhost +curl -s localhost | grep -i "collections" + +# Test specific endpoints +curl localhost/api/items +curl localhost/api/categories +``` + +### Debugging +```bash +# Check Apache error logs +incus exec www -- tail -f /var/log/apache2/error.log + +# Check access logs +incus exec www -- tail -f /var/log/apache2/access.log + +# Verify file permissions +incus exec www -- ls -la /var/www/localhost/ +``` + +## File Permissions + +### Container Permissions +- **Project files**: `ubuntu:www-data` ownership +- **Database**: `ubuntu:ubuntu` ownership +- **Apache**: Runs as `www-data` user (group access) + +### Common Issues +- Database permission errors: Check `collections.sqlite` ownership +- 403 errors: Verify file permissions in container +- 500 errors: Check Apache error logs + +## Environment Variables & Configuration + +### PHP Configuration +- Located in container PHP configuration +- Error reporting configured for development +- SQLite extension enabled + +### Application Config +- `config.php`: Database path and constants +- `SUPPORTED_LOCALES`: ['en', 'nl'] for i18n +- `DB_PATH`: Points to SQLite database file + +## Deployment Notes + +### Container Persistence +- Data persists in container filesystem +- Database file stored in project directory +- No external database dependencies + +### Backup Strategy +- Git repository for code +- SQLite database backup: `collections.sqlite.backup` +- Regular snapshots of LXC container recommended + +## Troubleshooting + +### Common Issues +1. **Apache not responding**: Check service status in container +2. **Database errors**: Verify SQLite file permissions +3. **404 errors**: Check `.htaccess` and routing configuration +4. **Permission denied**: Ensure `www-data` can read project files + +### Recovery Commands +```bash +# Restart Apache +incus exec www -- systemctl restart apache2 + +# Fix permissions (if needed) +incus exec www -- chown -R ubuntu:www-data /var/www/localhost/ +incus exec www -- chmod -R 755 /var/www/localhost/ + +# Check container status +incus list +incus info www +``` + +## Security Considerations + +- Container isolation via LXC +- No direct database access from host +- Apache runs with limited privileges +- SQLite database in project directory (not web-exposed) + +## Performance Notes + +- Lightweight SQLite database +- Minimal PHP dependencies +- Efficient routing with FastRoute +- Client-side caching for static assets \ No newline at end of file diff --git a/collections.sqlite b/collections.sqlite new file mode 100755 index 0000000..348bd4b Binary files /dev/null and b/collections.sqlite differ diff --git a/config.php b/config.php index ee13014..898e4e7 100755 --- a/config.php +++ b/config.php @@ -46,6 +46,6 @@ $twig->addGlobal('app_name', APP_NAME); $twig->addGlobal('delete_part_confirm', $translator->trans('Are you sure you want to delete this part?')); $twig->addGlobal('delete_category_confirm', $translator->trans('Are you sure you want to delete this category?')); -// Build category tree for sidebar -$sidebarHtml = ''; +// Sidebar will be built dynamically via AJAX +$sidebarHtml = ''; $twig->addGlobal('sidebar_html', $sidebarHtml); diff --git a/public/index.php b/public/index.php index 624f94e..4436344 100755 --- a/public/index.php +++ b/public/index.php @@ -13,6 +13,7 @@ use App\Router; use App\Controllers\ItemController; use App\Controllers\CategoryController; use App\Database; +use App\Models\Category; // Initialize Database (ensures tables exist) Database::getInstance(); @@ -40,25 +41,58 @@ $router->addRoute('GET', '/print/{id:\d+}', [ItemController::class, 'printQR']); // These routes return only the Twig block content, not the full layout. $router->addRoute('GET', '/api/items', [ItemController::class, 'listItems']); $router->addRoute('GET', '/api/items/{id:\d+}', [ItemController::class, 'getItem']); -$router->addRoute('GET', '/api/categories', [CategoryController::class, 'listCategories']); $router->addRoute('GET', '/api/categories/list', [CategoryController::class, 'listCategoriesJson']); $router->addRoute('GET', '/api/categories/{id}', [CategoryController::class, 'getCategory']); -$router->addRoute('GET', '/api/parts', [ItemController::class, 'renderAddForm']); + +// Categories with optional filter +$router->addRoute('GET', '/api/categories', function() { + $categoryId = $_GET['category'] ?? null; + if ($categoryId) { + // Show category details with items + $controller = new CategoryController(); + $controller->showCategory($categoryId); + } else { + // Show all categories + $controller = new CategoryController(); + $controller->listCategories(); + } +}); + +// Parts with optional item filter +$router->addRoute('GET', '/api/parts', function() { + $itemId = $_GET['item'] ?? null; + if ($itemId) { + // Show item details/edit form + $controller = new ItemController(); + $controller->editItem($itemId); + } else { + // Show add form + $controller = new ItemController(); + $controller->renderAddForm(); + } +}); $router->addRoute('GET', '/api/tree', function() { try { - $db = App\Database\Database::getInstance(); - $categories = App\Models\Category::getAll($db); + $db = Database::getInstance(); + $categories = Category::getAll($db); - function build_category_tree($categories, $parentId = null, &$visited = [], $depth = 0, &$nodeCount = 0) { + function build_category_tree($categories, $db, $parentId = null, $visited = [], $depth = 0, $nodeCount = 0) { if ($depth > 5 || $nodeCount > 100) return []; $tree = []; foreach ($categories as $cat) { if ($cat['parent_id'] == $parentId && !in_array($cat['id'], $visited)) { $visited[] = $cat['id']; + + // Get items for this category + $itemsStmt = $db->prepare('SELECT id, name FROM items WHERE category_id = :category_id ORDER BY name'); + $itemsStmt->execute([':category_id' => $cat['id']]); + $items = $itemsStmt->fetchAll(PDO::FETCH_ASSOC); + $node = [ 'id' => $cat['id'], 'name' => $cat['name'], - 'children' => build_category_tree($categories, $cat['id'], $visited, $depth + 1, $nodeCount) + 'items' => $items, + 'children' => build_category_tree($categories, $db, $cat['id'], $visited, $depth + 1, $nodeCount) ]; $tree[] = $node; $nodeCount++; @@ -72,21 +106,39 @@ $router->addRoute('GET', '/api/tree', function() { if ($depth > 5) return ''; $html = ''; foreach ($nodes as $node) { - $html .= '
  • '; - $html .= '' . htmlspecialchars($node['name']) . ''; + $html .= '