diff --git a/docs/wiki/Architecture.md b/docs/wiki/Architecture.md new file mode 100644 index 0000000..3611a73 --- /dev/null +++ b/docs/wiki/Architecture.md @@ -0,0 +1,18 @@ +# Architecture + +This project follows an Admin / Site separation with Inertia and React for the frontend and Laravel for the backend. + +Key boundaries +- Backend: controllers under `app/Http/Controllers/Admin/*` and `app/Http/Controllers/Site/*`. +- Routes: `routes/admin.php` (admin-only), `routes/web.php` (site/public), `routes/api.php` (API endpoints). +- Frontend pages: `resources/js/pages/admin/*`, `resources/js/pages/site/*`. +- Vite entries: `resources/js/entries/admin.tsx` and `resources/js/entries/site.tsx`. + +Services & Patterns +- ImageService: centralized image processing and validation in `app/Services/ImageService.php`. +- CacheService: centralized cache key generation and invalidation in `app/Services/CacheService.php`. +- FormRequests: validation lives in `app/Http/Requests/*` (Admin and Site namespaces). +- Repositories & Services: preferred to inject interfaces where heavy business logic is needed. + +Testing & Quality +- Pest for tests, Pint for PHP formatting, PHPStan for static analysis, ESLint for frontend linting. diff --git a/docs/wiki/Cache-Management.md b/docs/wiki/Cache-Management.md new file mode 100644 index 0000000..391a1d2 --- /dev/null +++ b/docs/wiki/Cache-Management.md @@ -0,0 +1,16 @@ +# Cache Management + +This project uses a centralized `CacheService` to avoid global cache flushes and to provide tag-aware invalidation when available. + +Key APIs + +- `cache_service()->rememberUsersList($page, $perPage, $ttl, $callback)` — remembers paginated users lists. +- `cache_service()->clearUsersList()` — clears users list cache using tags when available or explicit keys fallback. + +Cache key pattern + +- `users_list_page_{page}_per_{perPage}` + +Notes +- Avoid `Cache::flush()` in production. Use targeted `Cache::forget()` or tags so other caches are not cleared. +- The `CacheService` falls back to iterating sensible pagination ranges if the store doesn't support tags. diff --git a/docs/wiki/Getting-Started.md b/docs/wiki/Getting-Started.md new file mode 100644 index 0000000..e5f6052 --- /dev/null +++ b/docs/wiki/Getting-Started.md @@ -0,0 +1,42 @@ +# Getting Started + +This section helps developers set up the project locally. + +Prerequisites +- PHP 8.2+ +- Composer +- Node.js & npm +- SQLite or configured DB + +Quick local setup (copy/paste): + +```bash +composer install +cp .env.example .env +php artisan key:generate +php artisan storage:link +npm install +npm run dev +``` + +Run migrations and seeders (if needed): + +```bash +php artisan migrate +php artisan db:seed +``` + +Run tests: + +```bash +# Run all tests +./vendor/bin/pest + +# Run specific tests quickly +./vendor/bin/pest tests/Feature/Admin/UserAvatarUpdateTest.php +``` + +Notes +- The repository uses Laravel 12 + Inertia + React. +- Admin pages live under `routes/admin.php` and `app/Http/Controllers/Admin`. +- Frontend Inertia pages are in `resources/js/pages/admin` and `resources/js/pages/site`. diff --git a/docs/wiki/Home.md b/docs/wiki/Home.md new file mode 100644 index 0000000..894bf38 --- /dev/null +++ b/docs/wiki/Home.md @@ -0,0 +1,13 @@ +# Project Wiki — Home + +Welcome to the local copy of the project wiki. This acts as a documentation hub for the `fullstack-laravel-react-starter` repository. + +Sections +- Getting Started — development setup and key commands +- Architecture — outline of folder structure and design patterns +- Image Upload — how avatars are processed, validation, and usage +- ImageService API — detailed API and examples for `App\Services\ImageService` +- Cache Management — safe caching patterns and `CacheService` +- Testing — how to run tests locally and CI notes + +If you want these pages visible on GitHub's wiki, follow the instructions in `PUBLISH_TO_GITHUB_WIKI.md`. diff --git a/docs/wiki/Image-Upload.md b/docs/wiki/Image-Upload.md new file mode 100644 index 0000000..f752725 --- /dev/null +++ b/docs/wiki/Image-Upload.md @@ -0,0 +1,33 @@ +# Image Upload (Avatars) + +This project centralizes image handling through `App\Services\ImageService`. + +Key points + +- All images are validated with multiple security layers (MIME, extension, getimagesize, Intervention read, size limit). +- Avatars are converted to WebP and resized to 200x200 using `processImageWithDimensions()`. +- Controllers pass `storagePath` and dimensions to the image service; the service returns filename-only (e.g. `avatar-123.webp`). +- Files are stored on the `public` disk under `storage/app/public/{storagePath}`. Public URL: `/storage/{storagePath}/{filename}`. + +Usage example (controller): + +```php +$data['image'] = $this->imageService->processImageWithDimensions( + file: $request->file('image'), + storagePath: 'users', + width: 200, + height: 200, + prefix: 'avatar', + quality: 85 +); +``` + +Deletion +- To delete previous images call `imageService->deleteImageFile('users/'.$user->image)`. + +Notes and troubleshooting +- Ensure `php artisan storage:link` has been run so `/storage` is available. +- If images are not saved, check: + - Frontend sends multipart/form-data (Inertia `forceFormData: true` plus `_method: 'PUT'` for updates). + - Check logs for `ImageService` errors (they are logged). + - Confirm `storage/app/public/users` exists and is writeable. diff --git a/docs/wiki/ImageService-API.md b/docs/wiki/ImageService-API.md new file mode 100644 index 0000000..f96cde5 --- /dev/null +++ b/docs/wiki/ImageService-API.md @@ -0,0 +1,55 @@ +# ImageService API + +This document provides a concise API reference for `App\Services\ImageService`. + +Overview +- `ImageService` centralizes validation, resizing, conversion (to WebP), and deletion. +- It uses Intervention Image (Imagick driver when available) and Laravel's `Storage` facade. + +Main methods + +1. processImageWithDimensions(UploadedFile $file, string $storagePath, int $width, int $height, ?string $prefix = null, int $quality = 85): string|false +- Purpose: Convert the uploaded file to WebP, resize (cover) to exact dimensions, store under the `public` disk at `{storagePath}/{filename}.webp` and return the filename only. +- Returns: filename on success (e.g., `avatar-abc123.webp`), or throws an Exception on failure. +- Example: +```php +$filename = $imageService->processImageWithDimensions( + $request->file('image'), + 'users', + 200, + 200, + 'avatar', + 85 +); +// Save $filename to DB in users.image column +``` + +2. deleteImageFile(?string $imagePath): bool +- Purpose: Delete an image from the `public` disk by relative path (e.g., `users/avatar-abc123.webp`). +- Example: +```php +// If DB stores filename only, prepend folder +$imageService->deleteImageFile('users/'.$user->image); +``` + +3. processImage(...) and processImageLogo(...) +- Variants for other formats (logo PNG) and slightly different behaviors are available. See `app/Services/ImageService.php` for full signatures. + +Validation +- The service validates with multiple layers: + 1. MIME type + 2. File extension + 3. getimagesize() based header check + 4. Intervention Image read + 5. File size (10MB max) + +Error handling +- `processImageWithDimensions()` will throw an Exception when validation or processing fails. Controllers should catch and return a user-friendly message. + +Best practices +- Controllers should pass only the storage folder name and dimensions (keep size/paths in controllers). +- Store only filename in DB (e.g., `avatar-xxx.webp`). Build public URL using `asset('storage/{folder}/' . $filename)` or a model accessor like `getImageUrlAttribute()`. +- Ensure `php artisan storage:link` is run for public access. + +Notes +- Imagick/WebP availability depends on the environment. In test/CI environments without WebP support, Intervention may output JPEG; tests should allow for that fallback if strict WebP can't be guaranteed. diff --git a/docs/wiki/PUBLISH_TO_GITHUB_WIKI.md b/docs/wiki/PUBLISH_TO_GITHUB_WIKI.md new file mode 100644 index 0000000..6556b3e --- /dev/null +++ b/docs/wiki/PUBLISH_TO_GITHUB_WIKI.md @@ -0,0 +1,31 @@ +# How to Publish these pages to the GitHub Wiki + +The GitHub Wiki is a separate git repository. To publish these local pages to the wiki, follow these steps. + +1. Clone the wiki repo (replace owner/repo): + +```bash +# Clone the wiki remote +git clone https://github.com//.wiki.git repo-wiki +``` + +2. Copy or sync files from this repo's `docs/wiki/` into the cloned wiki repo: + +```bash +cp -R docs/wiki/* repo-wiki/ +cd repo-wiki +``` + +3. Commit and push: + +```bash +git add . +git commit -m "Update wiki pages" +git push origin main +``` + +4. Verify the wiki on GitHub at: `https://github.com///wiki`. + +Notes: +- If your repository is private, you may need to use an SSH remote or a token-enabled HTTPS remote. +- Alternatively, create a PR against a `docs` or `wiki` branch in the main repo and share changes for review before publishing. diff --git a/docs/wiki/README.md b/docs/wiki/README.md new file mode 100644 index 0000000..2c06025 --- /dev/null +++ b/docs/wiki/README.md @@ -0,0 +1,22 @@ +# Project Wiki (local copy) + +This folder contains a local copy of the repository's wiki pages. You can edit these files here and then publish them to the GitHub wiki remote if you want the pages visible as the project's GitHub Wiki. + +Why keep a local copy? +- Easier code review in PRs before publishing to the public wiki. +- Keep documentation versioned alongside code. +- CI can verify docs or build a docs site from these sources. + +How this folder maps to the GitHub wiki +- The GitHub wiki is its own git repository at `https://github.com//.wiki.git`. +- To publish these files, clone the wiki repo, copy the files from this folder into it, commit and push. See `PUBLISH_TO_GITHUB_WIKI.md` for exact commands. + +Structure +- Home.md — Overview and links +- Getting-Started.md — Local dev setup and common commands +- Architecture.md — High-level architecture and conventions +- Image-Upload.md — ImageService usage, validation and controller patterns +- Cache-Management.md — CacheService usage and cache key patterns +- Testing.md — Running tests, the new avatar upload tests + +Please review and update content before publishing. diff --git a/docs/wiki/Testing.md b/docs/wiki/Testing.md new file mode 100644 index 0000000..7e8bdb2 --- /dev/null +++ b/docs/wiki/Testing.md @@ -0,0 +1,21 @@ +# Testing + +Unit and Feature tests are written with Pest. + +Run tests: + +```bash +# Run all tests +./vendor/bin/pest + +# Run a single test file +./vendor/bin/pest tests/Feature/Admin/UserAvatarUpdateTest.php +``` + +New tests added: +- `tests/Feature/Admin/UserAvatarUpdateTest.php` — verifies avatar upload stores filename and file, and that old avatars are cleaned up. +- `tests/Feature/Admin/UserAvatarUploadNegativeTest.php` — verifies oversized, non-image, and JSON submission cases are rejected. + +Tips +- Use `Storage::fake('public')` in tests that touch filesystem to avoid real disk writes. +- If WebP conversion fails in your environment, tests accept JPEG as a fallback MIME for robustness.