Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/wiki/Architecture.md
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 16 additions & 0 deletions docs/wiki/Cache-Management.md
Original file line number Diff line number Diff line change
@@ -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.
42 changes: 42 additions & 0 deletions docs/wiki/Getting-Started.md
Original file line number Diff line number Diff line change
@@ -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`.
13 changes: 13 additions & 0 deletions docs/wiki/Home.md
Original file line number Diff line number Diff line change
@@ -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`.
33 changes: 33 additions & 0 deletions docs/wiki/Image-Upload.md
Original file line number Diff line number Diff line change
@@ -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.
55 changes: 55 additions & 0 deletions docs/wiki/ImageService-API.md
Original file line number Diff line number Diff line change
@@ -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.
31 changes: 31 additions & 0 deletions docs/wiki/PUBLISH_TO_GITHUB_WIKI.md
Original file line number Diff line number Diff line change
@@ -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/<owner>/<repo>.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/<owner>/<repo>/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.
22 changes: 22 additions & 0 deletions docs/wiki/README.md
Original file line number Diff line number Diff line change
@@ -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/<owner>/<repo>.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.
21 changes: 21 additions & 0 deletions docs/wiki/Testing.md
Original file line number Diff line number Diff line change
@@ -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.