diff --git a/STYLEGUIDE.md b/STYLEGUIDE.md index 9c6a2200a68a8fb886b86f2b5155bf25f9dad9e0..9eab4487e4f5435875b427723c1c4f0eaba9bb8c 100644 --- a/STYLEGUIDE.md +++ b/STYLEGUIDE.md @@ -27,8 +27,8 @@ - [Testing Practices](#testing-practices) - [Deployment and Environment Configuration](#deployment-and-environment-configuration) - ## General Principles + * Consistency: Maintain consistent coding styles and conventions across the entire codebase. * Readability: Write clear and understandable code with meaningful names and comments where necessary. * Modularity: Keep code modular to promote reusability and ease of maintenance. @@ -39,6 +39,7 @@ ## Project Structure ### Laravel Directory Structure + * app/: Core application code (Models, Controllers, Services). * resources/views/: Minimal Blade templates, primarily for initial page loads. * routes/: Define application routes (web.php, api.php). @@ -46,6 +47,7 @@ * public/: Public assets (images, scripts, styles). ### Inertia.js with SSR React Structure + * resources/js/: All JavaScript and React code. * components/: Reusable React components. * layouts/: Layout components for different page structures. @@ -180,7 +182,6 @@ $greeting = "Hi, I am {$name}."; $greeting = 'Hi, I am ' . $name . '.'; ``` - ## Ternary operators Every portion of a ternary expression should be on its own line unless it's a really short expression. @@ -229,7 +230,6 @@ if (! $goodCondition) { // do work ``` - ```php // Bad @@ -278,12 +278,10 @@ else { } ``` - ### Compound ifs In general, separate `if` statements should be preferred over a compound condition. This makes debugging code easier. - ```php // Good if (! $conditionA) { @@ -308,8 +306,6 @@ if ($conditionA && $conditionB && $conditionC) { } ``` - - ## Comments Comments should be avoided as much as possible by writing expressive code. If you do need to use a comment, format it like this: @@ -592,7 +588,6 @@ public function rules() } ``` - All custom validation rules must use snake_case: ```php @@ -713,57 +708,65 @@ Policy (required) Observer (as needed) - - * Define $fillable or $guarded properties explicitly. * Use query scopes for reusable query logic. * Name relationships clearly (e.g., public function orders()). ### Middleware + * Use middleware for cross-cutting concerns (authentication, logging). * Name middleware descriptively (e.g., EnsureUserIsAdmin). ## Inertia.js and React Standards ### Component Structure + * Use function components with React Hooks. * Organize components logically within components/, layouts/, and pages/. * Keep components small and focused on a single responsibility. ### Naming Conventions + * Components: PascalCase (e.g., UserProfile). * Props and State: camelCase (e.g., isLoading). * Hooks: Start custom hooks with use (e.g., useFetchData). ### State Management + * @todo TBD contextapi or redux * Use useReducer for complex state logic. ### SSR Considerations + * Ensure components are isomorphic when possible. * Avoid accessing browser-specific APIs during server-side rendering. * Handle hydration issues by matching server and client-rendered content. ### Styling + * Use CSS Modules, Styled Components, or Tailwind CSS for styling. * Follow consistent naming conventions for CSS classes. * Avoid inline styles unless necessary. ### API Calls + * Use Inertia’s methods for navigation and form submissions. * Handle API responses and errors gracefully. * Show loading indicators during asynchronous operations. ### Props and State Management + * Use PropTypes or TypeScript interfaces for type checking. * Pass only necessary props to components. * Avoid unnecessary re-renders by using React.memo and useCallback where appropriate. ### Translation with i18n + * i18n package enables use to translate the app in different languages. * Language files are stored in the i18n/locale folder(en, fr, sw, ...) * Usage. * Add the key value pairs in the language files. + ```js { "app" : { @@ -771,30 +774,36 @@ Observer (as needed) } } ``` + * Then access it in components. + ```js import { useTranslation } from 'react-i18next'; - + const { t } = useTranslation(); - + <p>{t('app.appLogoAlt')}</p> ``` + * Use camelCase for the keys that hold the translated strings. * All relevant strings should be translated. ## Database Conventions (PostgreSQL) ### Migrations + * Use descriptive names for migration files (e.g., 2023_01_01_000000_create_users_table.php). * Always write a down method to reverse migrations. Stub method with comment justification in lieu of omitting entirely. * Use appropriate data types and constraints. ### Schema Design + * Use snake_case for table and column names. * Normalize data where appropriate but consider performance implications. * Define foreign keys and indexes explicitly. ### Eloquent Relationships + * Define all relationships using Eloquent methods (hasOne, belongsToMany, etc.). * Use consistent naming for relationship methods. * Leverage eager loading to prevent N+1 query problems but be wary of eager loading on the model directly. @@ -802,43 +811,51 @@ Observer (as needed) ## Caching and Queueing (Redis) ### Caching + * Use Redis for caching frequently accessed data. * Implement cache invalidation strategies when data changes. * Use cache tags and keys that reflect the cached data’s purpose. ### Queueing + * Use Redis as the queue driver for background jobs. * Name queues based on functionality (e.g., emails, notifications). * Handle failed jobs by implementing retries and logging. ### Session Management + * Store sessions in Redis for scalability. * Configure session lifetimes appropriately. ## Testing Practices ### Backend Testing (PHPUnit) + * Write unit tests for models, services, and controllers. * Use feature tests to simulate HTTP requests and responses. * Mock external services and dependencies. ### Frontend Testing (Jest, React Testing Library) + * Write unit tests for React components. * Test user interactions and component rendering. * Avoid testing implementation details; focus on output and behavior. ### Code Coverage + * Aim for comprehensive test coverage, focusing on critical code paths. * Do not compromise code readability for the sake of coverage percentages. ## Deployment and Environment Configuration ### Environment Variables + * Use .env files for environment-specific settings. * Never commit .env files to version control unless it's encrypted * Access environment variables using Laravel’s env() helper only in config files. To access value in codebase, use laravel `config90` helper. ### Configuration Files + * Keep all configuration in the config/ directory. * Use environment variables within config files for sensitive data. * Document any custom configuration options. diff --git a/application/app/DataTransferObjects/UserData.php b/application/app/DataTransferObjects/UserData.php new file mode 100644 index 0000000000000000000000000000000000000000..8031edf46407b64dc2385dab1377f89242f83eae --- /dev/null +++ b/application/app/DataTransferObjects/UserData.php @@ -0,0 +1,22 @@ +<?php + +namespace App\DataTransferObjects; + +use Spatie\LaravelData\Data; +use Spatie\TypeScriptTransformer\Attributes\TypeScript; + +#[TypeScript] +class UserData extends Data +{ + public function __construct( + public int $id, + + public string $name, + + public string $email, + + public string $profile_photo_url, + + public string $email_verified_at + ) {} +} diff --git a/application/app/Enums/PermissionEnum.php b/application/app/Enums/PermissionEnum.php new file mode 100644 index 0000000000000000000000000000000000000000..86912ba5f4687b56079da1700ec1d989d8ddf17c --- /dev/null +++ b/application/app/Enums/PermissionEnum.php @@ -0,0 +1,64 @@ +<?php + +declare(strict_types=1); + +namespace App\Enums; + +use Spatie\Enum\Laravel\Enum; + +/** + * @method static self create_catalyst_communities() + * @method static self create_catalyst_groups() + * @method static self create_ideascale_profiles() + * @method static self create_events() + * @method static self create_funds() + * @method static self create_links() + * @method static self create_proposals() + * @method static self create_ratings() + * @method static self create_reviews() + * @method static self create_rewards() + * @method static self create_roles() + * @method static self create_users() + * @method static self create_votes() + * @method static self read_catalyst_communities() + * @method static self read_catalyst_groups() + * @method static self read_ideascale_profiles() + * @method static self read_funds() + * @method static self read_media() + * @method static self read_permissions() + * @method static self read_proposals() + * @method static self read_ratings() + * @method static self read_reviews() + * @method static self read_roles() + * @method static self read_users() + * @method static self update_catalyst_communities() + * @method static self update_catalyst_groups() + * @method static self update_ideascale_profiles() + * @method static self update_admins() + * @method static self update_funds() + * @method static self update_media() + * @method static self update_proposals() + * @method static self update_ratings() + * @method static self update_reviews() + * @method static self update_roles() + * @method static self update_users() + * @method static self delete_catalyst_communities() + * @method static self delete_catalyst_groups() + * @method static self delete_catalyst_users() + * @method static self delete_admins() + * @method static self delete_funds() + * @method static self delete_permissions() + * @method static self delete_proposals() + * @method static self delete_ratings() + * @method static self delete_reviews() + * @method static self delete_rewards() + * @method static self delete_roles() + * @method static self delete_users() + */ +final class PermissionEnum extends Enum +{ + protected static function values(): \Closure + { + return fn(string $name): string|int => str_replace('_', ' ', mb_strtolower($name)); + } +} diff --git a/application/app/Enums/RoleEnum.php b/application/app/Enums/RoleEnum.php new file mode 100644 index 0000000000000000000000000000000000000000..7385757e57119920f64ada93da000dccce0c4453 --- /dev/null +++ b/application/app/Enums/RoleEnum.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); + +namespace App\Enums; + +use Spatie\Enum\Laravel\Enum; + +/** + * @method static self contributor() + * @method static self viewer() + * @method static self editor() + * @method static self admin() + * @method static self super_admin() + */ +final class RoleEnum extends Enum +{ + protected static function values(): \Closure + { + return fn(string $name): string|int => str_replace('_', ' ', mb_strtolower($name)); + } +} diff --git a/application/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/application/app/Http/Controllers/Auth/AuthenticatedSessionController.php index 9b4b92d6eec6ec6c87c1038408155cdad34539d4..4d8ae46c3cca3aa4fa6e765bdc0b141c8c1dec7e 100644 --- a/application/app/Http/Controllers/Auth/AuthenticatedSessionController.php +++ b/application/app/Http/Controllers/Auth/AuthenticatedSessionController.php @@ -8,7 +8,6 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Route; -use Laravolt\Avatar\Facade as Avatar; use Inertia\Inertia; use Inertia\Response; @@ -34,12 +33,6 @@ public function store(LoginRequest $request): RedirectResponse $request->session()->regenerate(); - $user = Auth::user(); - - $avatar = Avatar::create($user->name)->toBase64(); - - $request->session()->put('avatar', $avatar); - return redirect()->intended(route('dashboard', absolute: false)); } diff --git a/application/app/Http/Middleware/HandleInertiaRequests.php b/application/app/Http/Middleware/HandleInertiaRequests.php index 3cfcd34618f0632b432192bc18c2e0de9703a445..17ab36dc3d4be20483f35f7abcc100d5c6e2dd40 100644 --- a/application/app/Http/Middleware/HandleInertiaRequests.php +++ b/application/app/Http/Middleware/HandleInertiaRequests.php @@ -4,6 +4,7 @@ namespace App\Http\Middleware; +use App\DataTransferObjects\UserData; use Illuminate\Http\Request; use Inertia\Middleware; @@ -34,8 +35,7 @@ public function share(Request $request): array return [ ...parent::share($request), 'auth' => [ - 'user' => $request->user(), - 'avatar' => $request->session()->get('avatar') + 'user' => $request->user() ? UserData::from($request->user()) : null, ], ]; } diff --git a/application/app/Models/Model.php b/application/app/Models/Model.php new file mode 100644 index 0000000000000000000000000000000000000000..03f87af0b0ae0c62f497d277407d814a2d4d2331 --- /dev/null +++ b/application/app/Models/Model.php @@ -0,0 +1,13 @@ +<?php + +declare(strict_types=1); + +namespace App\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model as EloquentModel; + +class Model extends EloquentModel +{ + use HasFactory; +} diff --git a/application/app/Models/Proposal.php b/application/app/Models/Proposal.php index 8f167f4fbebb428e69467067c583cfbd7d7bf01a..665e8b0eb4797e96393ea2d879e3ab6fc13580f7 100644 --- a/application/app/Models/Proposal.php +++ b/application/app/Models/Proposal.php @@ -1,10 +1,12 @@ <?php +declare(strict_types=1); + namespace App\Models; +use App\Models\Model; use App\Enums\CatalystCurrencies; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; class Proposal extends Model { diff --git a/application/app/Models/User.php b/application/app/Models/User.php index 7d4ed6e1632239d1df8e39b3dfbff09d56c940f4..49705091f9ae7b4def56cac72cf1a54538f9dae9 100644 --- a/application/app/Models/User.php +++ b/application/app/Models/User.php @@ -4,13 +4,21 @@ namespace App\Models; +use Laravolt\Avatar\Facade as Avatar; +use Spatie\MediaLibrary\HasMedia; +use Illuminate\Auth\MustVerifyEmail; +use Spatie\Image\Enums\CropPosition; +use Spatie\Permission\Traits\HasRoles; +use Illuminate\Notifications\Notifiable; +use Spatie\MediaLibrary\InteractsWithMedia; +use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Spatie\MediaLibrary\MediaCollections\Models\Media; use Illuminate\Foundation\Auth\User as Authenticatable; -use Illuminate\Notifications\Notifiable; -class User extends Authenticatable +class User extends Authenticatable implements HasMedia { - use HasFactory, Notifiable; + use HasFactory, Notifiable, HasRoles, InteractsWithMedia, MustVerifyEmail; /** * The attributes that are mass assignable. @@ -45,4 +53,38 @@ protected function casts(): array 'password' => 'hashed', ]; } + + public function gravatar(): Attribute + { + return Attribute::make( + get: fn() => Avatar::create($this->email)->toGravatar() + ); + } + + public function profilePhotoUrl(): Attribute + { + return Attribute::make( + get: fn() => count($this->getMedia('profile')) ? $this->getMedia('profile')[0]->getFullUrl() : $this->gravatar + ); + } + + public function registerMediaConversions(?Media $media = null): void + { + $this->addMediaConversion('thumbnail') + ->width(150) + ->height(150) + ->withResponsiveImages() + ->crop(150, 150, CropPosition::Top) + ->performOnCollections('profile') + ->useFallbackUrl($this->gravatar); + + $this->addMediaConversion('large') + ->width(1080) + ->height(1350) + ->crop(1080, 1350, CropPosition::Top) + ->withResponsiveImages() + ->performOnCollections('profile') + ->useFallbackUrl($this->gravatar); + } + } diff --git a/application/app/Observers/ProposalObserver.php b/application/app/Observers/ProposalObserver.php index e575fa5953083d540f135fb902020a90eefb0a94..d78707478199798fe5d08f1f3804ec99dddc213d 100644 --- a/application/app/Observers/ProposalObserver.php +++ b/application/app/Observers/ProposalObserver.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace App\Observers; use App\Models\Proposal; diff --git a/application/app/Policies/AppPolicy.php b/application/app/Policies/AppPolicy.php new file mode 100644 index 0000000000000000000000000000000000000000..7eafe7ed9b69bb083d768ef9ab4903403c72f15e --- /dev/null +++ b/application/app/Policies/AppPolicy.php @@ -0,0 +1,119 @@ +<?php + +declare(strict_types=1); + +namespace App\Policies; + +use App\Models\User; +use App\Models\Model; +use App\Enums\RoleEnum; +use Illuminate\Auth\Access\HandlesAuthorization; + +class AppPolicy +{ + use HandlesAuthorization; + + /** + * Perform pre-authorization checks. + */ + public function before(User $user, string $ability): bool + { + return $user->hasAnyRole([RoleEnum::admin()->value, RoleEnum::super_admin()->value]); + } + + /** + * Determine whether the user can view any models. + */ + public function canViewAny(User $user): mixed + { + return $user->hasAnyRole([RoleEnum::admin()->value, RoleEnum::super_admin()->value]); + } + + /** + * Determine whether the user can view the model. + * @throws \Exception + */ + public function canView(User $user, $model): mixed + { + return $user->hasAnyRole([RoleEnum::admin()->value, RoleEnum::super_admin()->value]) || $this->ownsModel($user, $model); + } + + /** + * Determine whether the user can create models. + */ + public function canCreate(User $user): bool + { + return $user->hasAnyRole([RoleEnum::admin()->value, RoleEnum::super_admin()->value]); + } + + /** + * Determine whether the user can update the model. + */ + public function canUpdate(User $user, $model): mixed + { + return $user->hasAnyRole([RoleEnum::admin()->value, RoleEnum::super_admin()->value]) || $this->ownsModel($user, $model); + } + + /** Determine whether the user can update the model.*/ + public function canUpdateAny(User $user): mixed + { + return $user->hasAnyRole([RoleEnum::admin()->value, RoleEnum::super_admin()->value]); + } + + /** + * Determine whether the user can delete the model. + */ + public function canDelete(User $user, $model): bool + { + return $user->hasAnyRole([RoleEnum::admin()->value, RoleEnum::super_admin()->value]) || $this->ownsModel($user, $model); + } + + /** + * Determine whether the user can delete the model. + */ + public function canDeleteAny(User $user): mixed + { + return $user->hasAnyRole([RoleEnum::admin()->value, RoleEnum::super_admin()->value]); + } + + /** + * Determine whether the user can delete the model. + */ + public function canForceDelete(User $user, $model): bool + { + return $user->hasAnyRole([RoleEnum::super_admin()->value]); + } + + /** + * Determine whether the user can delete the model. + */ + public function canForceDeleteAny(User $user): mixed + { + return $user->hasAnyRole([RoleEnum::super_admin()->value]); + } + + /** + * Determine whether the user can delete the model. + */ + public function forceDelete(User $user, Model $model): mixed + { + return $user->hasAnyRole([RoleEnum::admin()->value, RoleEnum::super_admin()->value]); + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, User $model): bool + { + return $user->hasRole([RoleEnum::admin()->value, RoleEnum::super_admin()->value]); + } + + protected function ownsModel(User $user, $model): bool + { + if ($model instanceof User) { + return $user->id === $model->id; + } + + return $user->id === $model->user_id; + } +} diff --git a/application/app/Policies/UserPolicy.php b/application/app/Policies/UserPolicy.php new file mode 100644 index 0000000000000000000000000000000000000000..83866d517fdbdc0409929f978cc81212827db239 --- /dev/null +++ b/application/app/Policies/UserPolicy.php @@ -0,0 +1,53 @@ +<?php + +declare(strict_types=1); + +namespace App\Policies; + +use App\Models\User; +use App\Enums\RoleEnum; +use App\Enums\PermissionEnum; +use Illuminate\Auth\Access\Response; + +class UserPolicy extends AppPolicy +{ + /** + * Determine whether the user can view any models. + */ + public function viewAny(User $user): bool + { + return parent::canViewAny($user) || $user->hasAnyPermission([PermissionEnum::read_users()->value]); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, User $model): bool + { + return parent::canView($user, $model) || $user->hasAnyPermission([PermissionEnum::read_users()->value]); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return parent::canCreate($user) || $user->hasAnyPermission([PermissionEnum::create_users()->value]); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, User $model): bool + { + return parent::canUpdate($user, $model) || $user->hasAnyPermission([PermissionEnum::update_users()->value]); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, User $model): bool + { + return parent::canDelete($user, $model) || $user->hasAnyPermission([PermissionEnum::delete_users()->value]); + } +} diff --git a/application/app/Repositories/ProposalRepository.php b/application/app/Repositories/ProposalRepository.php index 25c3197ac2eb42c9e7132e52046224502cfc5604..75573951ae8831ae8c02f73ba32f4f1eb502c845 100644 --- a/application/app/Repositories/ProposalRepository.php +++ b/application/app/Repositories/ProposalRepository.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace App\Repositories; use App\Models\Proposal; diff --git a/application/app/Repositories/Repository.php b/application/app/Repositories/Repository.php index 45c441e9b1ca415442d00b4659f43640700bcf80..babc578d42e011a74732a810c0334b391ba59c9f 100644 --- a/application/app/Repositories/Repository.php +++ b/application/app/Repositories/Repository.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace App\Repositories; use App\Scopes\LimitScope; diff --git a/application/app/Repositories/RepositoryInterface.php b/application/app/Repositories/RepositoryInterface.php index 66a7d98154bc9f09701422d01e62bd9438a99a21..2aabd2930a86c45a35bc36225e939c4f915bcf4b 100644 --- a/application/app/Repositories/RepositoryInterface.php +++ b/application/app/Repositories/RepositoryInterface.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace App\Repositories; interface RepositoryInterface diff --git a/application/app/Scopes/LimitScope.php b/application/app/Scopes/LimitScope.php index 146465188f4af1912e593ab4bd9ba2313f62ff37..0ee54566242ed044abcbbe337c689afd5d632060 100644 --- a/application/app/Scopes/LimitScope.php +++ b/application/app/Scopes/LimitScope.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace App\Scopes; use Illuminate\Database\Eloquent\Builder; diff --git a/application/composer.json b/application/composer.json index cf70280415db81d96a7d9c50192206bb3780e8d8..6cfecd213d3a48079da6a7ed916ee553b2f7cc9f 100644 --- a/application/composer.json +++ b/application/composer.json @@ -14,6 +14,9 @@ "laravel/tinker": "^2.9", "laravolt/avatar": "^6.0", "spatie/laravel-data": "^4.11", + "spatie/laravel-enum": "^3.1", + "spatie/laravel-medialibrary": "^11.9", + "spatie/laravel-permission": "^6.10", "spatie/laravel-typescript-transformer": "^2.5", "tightenco/ziggy": "^2.0" }, diff --git a/application/composer.lock b/application/composer.lock index 794f089fdb686974853896c5a557d9fa776b53f2..d5744224eba141060dc8d62935600e1dd5f131fe 100644 --- a/application/composer.lock +++ b/application/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "12182aba8ac451efd365c7c9bd97324c", + "content-hash": "4fdd26c781ec96df3d4a35da294ccbe1", "packages": [ { "name": "amphp/amp", @@ -994,6 +994,87 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, { "name": "daverandom/libdns", "version": "v2.1.0", @@ -3641,6 +3722,83 @@ ], "time": "2024-03-23T07:42:40+00:00" }, + { + "name": "maennchen/zipstream-php", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "6187e9cc4493da94b9b63eb2315821552015fca9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/6187e9cc4493da94b9b63eb2315821552015fca9", + "reference": "6187e9cc4493da94b9b63eb2315821552015fca9", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.1" + }, + "require-dev": { + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^10.0", + "vimeo/psalm": "^5.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + } + ], + "time": "2024-10-10T12:33:01+00:00" + }, { "name": "monolog/monolog", "version": "3.7.0", @@ -5293,6 +5451,210 @@ }, "time": "2023-11-30T05:34:44+00:00" }, + { + "name": "spatie/enum", + "version": "3.13.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/enum.git", + "reference": "f1a0f464ba909491a53e60a955ce84ad7cd93a2c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/enum/zipball/f1a0f464ba909491a53e60a955ce84ad7cd93a2c", + "reference": "f1a0f464ba909491a53e60a955ce84ad7cd93a2c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.0" + }, + "require-dev": { + "fakerphp/faker": "^1.9.1", + "larapack/dd": "^1.1", + "phpunit/phpunit": "^9.0", + "vimeo/psalm": "^4.3" + }, + "suggest": { + "fakerphp/faker": "To use the enum faker provider", + "phpunit/phpunit": "To use the enum assertions" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Enum\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Roose", + "email": "brent@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Tom Witkowski", + "email": "dev@gummibeer.de", + "homepage": "https://gummibeer.de", + "role": "Developer" + } + ], + "description": "PHP Enums", + "homepage": "https://github.com/spatie/enum", + "keywords": [ + "enum", + "enumerable", + "spatie" + ], + "support": { + "docs": "https://docs.spatie.be/enum", + "issues": "https://github.com/spatie/enum/issues", + "source": "https://github.com/spatie/enum" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2022-04-22T08:51:55+00:00" + }, + { + "name": "spatie/image", + "version": "3.7.4", + "source": { + "type": "git", + "url": "https://github.com/spatie/image.git", + "reference": "d72d1ae07f91a3c1230e064acd4fd8c334ab237b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/image/zipball/d72d1ae07f91a3c1230e064acd4fd8c334ab237b", + "reference": "d72d1ae07f91a3c1230e064acd4fd8c334ab237b", + "shasum": "" + }, + "require": { + "ext-exif": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": "^8.2", + "spatie/image-optimizer": "^1.7.5", + "spatie/temporary-directory": "^2.2", + "symfony/process": "^6.4|^7.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-imagick": "*", + "laravel/sail": "^1.34", + "pestphp/pest": "^2.28", + "phpstan/phpstan": "^1.10.50", + "spatie/pest-plugin-snapshots": "^2.1", + "spatie/pixelmatch-php": "^1.0", + "spatie/ray": "^1.40.1", + "symfony/var-dumper": "^6.4|7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Image\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Manipulate images with an expressive API", + "homepage": "https://github.com/spatie/image", + "keywords": [ + "image", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/image/tree/3.7.4" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-10-07T09:03:34+00:00" + }, + { + "name": "spatie/image-optimizer", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/image-optimizer.git", + "reference": "4fd22035e81d98fffced65a8c20d9ec4daa9671c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/image-optimizer/zipball/4fd22035e81d98fffced65a8c20d9ec4daa9671c", + "reference": "4fd22035e81d98fffced65a8c20d9ec4daa9671c", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.3|^8.0", + "psr/log": "^1.0 | ^2.0 | ^3.0", + "symfony/process": "^4.2|^5.0|^6.0|^7.0" + }, + "require-dev": { + "pestphp/pest": "^1.21", + "phpunit/phpunit": "^8.5.21|^9.4.4", + "symfony/var-dumper": "^4.2|^5.0|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\ImageOptimizer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily optimize images using PHP", + "homepage": "https://github.com/spatie/image-optimizer", + "keywords": [ + "image-optimizer", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/image-optimizer/issues", + "source": "https://github.com/spatie/image-optimizer/tree/1.8.0" + }, + "time": "2024-11-04T08:24:54+00:00" + }, { "name": "spatie/laravel-data", "version": "4.11.1", @@ -5377,6 +5739,207 @@ ], "time": "2024-10-23T07:14:53+00:00" }, + { + "name": "spatie/laravel-enum", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-enum.git", + "reference": "423128aea07601873722940d3055dc9a2364a737" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-enum/zipball/423128aea07601873722940d3055dc9a2364a737", + "reference": "423128aea07601873722940d3055dc9a2364a737", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^8.0 || ^9.43 || ^10.0 || ^11.0", + "illuminate/contracts": "^8.0 || ^9.43 || ^10.0 || ^11.0", + "illuminate/database": "^8.0 || ^9.43 || ^10.0 || ^11.0", + "illuminate/http": "^8.0 || ^9.43 || ^10.0 || ^11.0", + "illuminate/support": "^8.0 || ^9.43 || ^10.0 || ^11.0", + "php": "^8.0", + "spatie/enum": "^3.9" + }, + "conflict": { + "bensampo/laravel-enum": "*" + }, + "require-dev": { + "fakerphp/faker": "^1.16", + "mockery/mockery": "^1.4.0", + "orchestra/testbench": "^6.0 || ^7.0 || ^8.0 || ^9.0", + "phpunit/phpunit": "^9.5 || ^10.0", + "vimeo/psalm": "^4.0 || ^5.0" + }, + "suggest": { + "fzaninotto/faker": "^1.9.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Enum\\Laravel\\EnumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\Enum\\Laravel\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Roose", + "email": "brent@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + }, + { + "name": "Tom Witkowski", + "email": "dev@gummibeer.de", + "homepage": "https://gummibeer.de", + "role": "Developer" + } + ], + "description": "Laravel Enum support", + "homepage": "https://github.com/spatie/laravel-enum", + "keywords": [ + "enum", + "laravel", + "laravel-enum", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-enum/issues", + "source": "https://github.com/spatie/laravel-enum/tree/3.1.0" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-06-12T08:02:01+00:00" + }, + { + "name": "spatie/laravel-medialibrary", + "version": "11.9.2", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-medialibrary.git", + "reference": "6a39eca52236bc1e1261f366d4022996521ae843" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/6a39eca52236bc1e1261f366d4022996521ae843", + "reference": "6a39eca52236bc1e1261f366d4022996521ae843", + "shasum": "" + }, + "require": { + "composer/semver": "^3.4", + "ext-exif": "*", + "ext-fileinfo": "*", + "ext-json": "*", + "illuminate/bus": "^10.0|^11.0", + "illuminate/conditionable": "^10.0|^11.0", + "illuminate/console": "^10.0|^11.0", + "illuminate/database": "^10.0|^11.0", + "illuminate/pipeline": "^10.0|^11.0", + "illuminate/support": "^10.0|^11.0", + "maennchen/zipstream-php": "^3.1", + "php": "^8.2", + "spatie/image": "^3.3.2", + "spatie/laravel-package-tools": "^1.16.1", + "spatie/temporary-directory": "^2.2", + "symfony/console": "^6.4.1|^7.0" + }, + "conflict": { + "php-ffmpeg/php-ffmpeg": "<0.6.1" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.293.10", + "ext-imagick": "*", + "ext-pdo_sqlite": "*", + "ext-zip": "*", + "guzzlehttp/guzzle": "^7.8.1", + "larastan/larastan": "^2.7", + "league/flysystem-aws-s3-v3": "^3.22", + "mockery/mockery": "^1.6.7", + "orchestra/testbench": "^7.0|^8.17|^9.0", + "pestphp/pest": "^2.28", + "phpstan/extension-installer": "^1.3.1", + "spatie/laravel-ray": "^1.33", + "spatie/pdf-to-image": "^2.2|^3.0", + "spatie/pest-plugin-snapshots": "^2.1" + }, + "suggest": { + "league/flysystem-aws-s3-v3": "Required to use AWS S3 file storage", + "php-ffmpeg/php-ffmpeg": "Required for generating video thumbnails", + "spatie/pdf-to-image": "Required for generating thumbnails of PDFs and SVGs" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\MediaLibrary\\MediaLibraryServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Spatie\\MediaLibrary\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Associate files with Eloquent models", + "homepage": "https://github.com/spatie/laravel-medialibrary", + "keywords": [ + "cms", + "conversion", + "downloads", + "images", + "laravel", + "laravel-medialibrary", + "media", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-medialibrary/issues", + "source": "https://github.com/spatie/laravel-medialibrary/tree/11.9.2" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-10-18T14:24:58+00:00" + }, { "name": "spatie/laravel-package-tools", "version": "1.16.5", @@ -5437,6 +6000,89 @@ ], "time": "2024-08-27T18:56:10+00:00" }, + { + "name": "spatie/laravel-permission", + "version": "6.10.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "2444bb914a52c570c00ae8c94e096a58e01b2317" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/2444bb914a52c570c00ae8c94e096a58e01b2317", + "reference": "2444bb914a52c570c00ae8c94e096a58e01b2317", + "shasum": "" + }, + "require": { + "illuminate/auth": "^8.12|^9.0|^10.0|^11.0", + "illuminate/container": "^8.12|^9.0|^10.0|^11.0", + "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0", + "illuminate/database": "^8.12|^9.0|^10.0|^11.0", + "php": "^8.0" + }, + "require-dev": { + "larastan/larastan": "^1.0|^2.0", + "laravel/passport": "^11.0|^12.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0", + "phpunit/phpunit": "^9.4|^10.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.x-dev", + "dev-master": "6.x-dev" + }, + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 8.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/6.10.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2024-11-05T17:30:49+00:00" + }, { "name": "spatie/laravel-typescript-transformer", "version": "2.5.0", @@ -5598,6 +6244,67 @@ ], "time": "2024-08-29T10:43:45+00:00" }, + { + "name": "spatie/temporary-directory", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/temporary-directory.git", + "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", + "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\TemporaryDirectory\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Vanderbist", + "email": "alex@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Easily create, use and destroy temporary directories", + "homepage": "https://github.com/spatie/temporary-directory", + "keywords": [ + "php", + "spatie", + "temporary-directory" + ], + "support": { + "issues": "https://github.com/spatie/temporary-directory/issues", + "source": "https://github.com/spatie/temporary-directory/tree/2.2.1" + }, + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2023-12-25T11:46:58+00:00" + }, { "name": "spatie/typescript-transformer", "version": "2.4.0", @@ -11200,6 +11907,6 @@ "platform": { "php": "^8.2" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/application/config/data.php b/application/config/data.php index 3be386d1b9fc0ead24e056ea100633e82ec46659..4e80b2087cdd3a8dc1f41d33439b530bacf1c693 100644 --- a/application/config/data.php +++ b/application/config/data.php @@ -173,7 +173,7 @@ * so the default 'Data` will end up as '\App\Data', and generated Data classes will be placed in the * app/Data/ folder. Data classes can live anywhere, but this is where `make:data` will put them. */ - 'namespace' => 'Data', + 'namespace' => 'DataTransferObjects', /** * This suffix will be appended to all data classes generated by make:data, so that they are less likely diff --git a/application/config/media-library.php b/application/config/media-library.php new file mode 100644 index 0000000000000000000000000000000000000000..8823bb2ae785478fc27a85a302001f4f2a0fccef --- /dev/null +++ b/application/config/media-library.php @@ -0,0 +1,280 @@ +<?php + +return [ + + /* + * The disk on which to store added files and derived images by default. Choose + * one or more of the disks you've configured in config/filesystems.php. + */ + 'disk_name' => env('MEDIA_DISK', 'public'), + + /* + * The maximum file size of an item in bytes. + * Adding a larger file will result in an exception. + */ + 'max_file_size' => 1024 * 1024 * 10, // 10MB + + /* + * This queue connection will be used to generate derived and responsive images. + * Leave empty to use the default queue connection. + */ + 'queue_connection_name' => env('QUEUE_CONNECTION', 'sync'), + + /* + * This queue will be used to generate derived and responsive images. + * Leave empty to use the default queue. + */ + 'queue_name' => env('MEDIA_QUEUE', ''), + + /* + * By default all conversions will be performed on a queue. + */ + 'queue_conversions_by_default' => env('QUEUE_CONVERSIONS_BY_DEFAULT', true), + + /* + * Should database transactions be run after database commits? + */ + 'queue_conversions_after_database_commit' => env('QUEUE_CONVERSIONS_AFTER_DB_COMMIT', true), + + /* + * The fully qualified class name of the media model. + */ + 'media_model' => Spatie\MediaLibrary\MediaCollections\Models\Media::class, + + /* + * When enabled, media collections will be serialised using the default + * laravel model serialization behaviour. + * + * Keep this option disabled if using Media Library Pro components (https://medialibrary.pro) + */ + 'use_default_collection_serialization' => false, + + /* + * The fully qualified class name of the model used for temporary uploads. + * + * This model is only used in Media Library Pro (https://medialibrary.pro) + */ + 'temporary_upload_model' => Spatie\MediaLibraryPro\Models\TemporaryUpload::class, + + /* + * When enabled, Media Library Pro will only process temporary uploads that were uploaded + * in the same session. You can opt to disable this for stateless usage of + * the pro components. + */ + 'enable_temporary_uploads_session_affinity' => true, + + /* + * When enabled, Media Library pro will generate thumbnails for uploaded file. + */ + 'generate_thumbnails_for_temporary_uploads' => true, + + /* + * This is the class that is responsible for naming generated files. + */ + 'file_namer' => Spatie\MediaLibrary\Support\FileNamer\DefaultFileNamer::class, + + /* + * The class that contains the strategy for determining a media file's path. + */ + 'path_generator' => Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator::class, + + /* + * The class that contains the strategy for determining how to remove files. + */ + 'file_remover_class' => Spatie\MediaLibrary\Support\FileRemover\DefaultFileRemover::class, + + /* + * Here you can specify which path generator should be used for the given class. + */ + 'custom_path_generators' => [ + // Model::class => PathGenerator::class + // or + // 'model_morph_alias' => PathGenerator::class + ], + + /* + * When urls to files get generated, this class will be called. Use the default + * if your files are stored locally above the site root or on s3. + */ + 'url_generator' => Spatie\MediaLibrary\Support\UrlGenerator\DefaultUrlGenerator::class, + + /* + * Moves media on updating to keep path consistent. Enable it only with a custom + * PathGenerator that uses, for example, the media UUID. + */ + 'moves_media_on_update' => false, + + /* + * Whether to activate versioning when urls to files get generated. + * When activated, this attaches a ?v=xx query string to the URL. + */ + 'version_urls' => false, + + /* + * The media library will try to optimize all converted images by removing + * metadata and applying a little bit of compression. These are + * the optimizers that will be used by default. + */ + 'image_optimizers' => [ + Spatie\ImageOptimizer\Optimizers\Jpegoptim::class => [ + '-m85', // set maximum quality to 85% + '--force', // ensure that progressive generation is always done also if a little bigger + '--strip-all', // this strips out all text information such as comments and EXIF data + '--all-progressive', // this will make sure the resulting image is a progressive one + ], + Spatie\ImageOptimizer\Optimizers\Pngquant::class => [ + '--force', // required parameter for this package + ], + Spatie\ImageOptimizer\Optimizers\Optipng::class => [ + '-i0', // this will result in a non-interlaced, progressive scanned image + '-o2', // this set the optimization level to two (multiple IDAT compression trials) + '-quiet', // required parameter for this package + ], + Spatie\ImageOptimizer\Optimizers\Svgo::class => [ + '--disable=cleanupIDs', // disabling because it is known to cause troubles + ], + Spatie\ImageOptimizer\Optimizers\Gifsicle::class => [ + '-b', // required parameter for this package + '-O3', // this produces the slowest but best results + ], + Spatie\ImageOptimizer\Optimizers\Cwebp::class => [ + '-m 6', // for the slowest compression method in order to get the best compression. + '-pass 10', // for maximizing the amount of analysis pass. + '-mt', // multithreading for some speed improvements. + '-q 90', //quality factor that brings the least noticeable changes. + ], + Spatie\ImageOptimizer\Optimizers\Avifenc::class => [ + '-a cq-level=23', // constant quality level, lower values mean better quality and greater file size (0-63). + '-j all', // number of jobs (worker threads, "all" uses all available cores). + '--min 0', // min quantizer for color (0-63). + '--max 63', // max quantizer for color (0-63). + '--minalpha 0', // min quantizer for alpha (0-63). + '--maxalpha 63', // max quantizer for alpha (0-63). + '-a end-usage=q', // rate control mode set to Constant Quality mode. + '-a tune=ssim', // SSIM as tune the encoder for distortion metric. + ], + ], + + /* + * These generators will be used to create an image of media files. + */ + 'image_generators' => [ + Spatie\MediaLibrary\Conversions\ImageGenerators\Image::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Webp::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Avif::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Pdf::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Svg::class, + Spatie\MediaLibrary\Conversions\ImageGenerators\Video::class, + ], + + /* + * The path where to store temporary files while performing image conversions. + * If set to null, storage_path('media-library/temp') will be used. + */ + 'temporary_directory_path' => storage_path('media-library/temp'), + + /* + * The engine that should perform the image conversions. + * Should be either `gd` or `imagick`. + */ + 'image_driver' => env('IMAGE_DRIVER', 'gd'), + + /* + * FFMPEG & FFProbe binaries paths, only used if you try to generate video + * thumbnails and have installed the php-ffmpeg/php-ffmpeg composer + * dependency. + */ + 'ffmpeg_path' => env('FFMPEG_PATH', '/usr/bin/ffmpeg'), + 'ffprobe_path' => env('FFPROBE_PATH', '/usr/bin/ffprobe'), + + /* + * Here you can override the class names of the jobs used by this package. Make sure + * your custom jobs extend the ones provided by the package. + */ + 'jobs' => [ + 'perform_conversions' => Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob::class, + 'generate_responsive_images' => Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob::class, + ], + + /* + * When using the addMediaFromUrl method you may want to replace the default downloader. + * This is particularly useful when the url of the image is behind a firewall and + * need to add additional flags, possibly using curl. + */ + 'media_downloader' => Spatie\MediaLibrary\Downloaders\DefaultDownloader::class, + + /* + * When using the addMediaFromUrl method the SSL is verified by default. + * This is option disables SSL verification when downloading remote media. + * Please note that this is a security risk and should only be false in a local environment. + */ + 'media_downloader_ssl' => env('MEDIA_DOWNLOADER_SSL', true), + + 'remote' => [ + /* + * Any extra headers that should be included when uploading media to + * a remote disk. Even though supported headers may vary between + * different drivers, a sensible default has been provided. + * + * Supported by S3: CacheControl, Expires, StorageClass, + * ServerSideEncryption, Metadata, ACL, ContentEncoding + */ + 'extra_headers' => [ + 'CacheControl' => 'max-age=604800', + ], + ], + + 'responsive_images' => [ + /* + * This class is responsible for calculating the target widths of the responsive + * images. By default we optimize for filesize and create variations that each are 30% + * smaller than the previous one. More info in the documentation. + * + * https://docs.spatie.be/laravel-medialibrary/v9/advanced-usage/generating-responsive-images + */ + 'width_calculator' => Spatie\MediaLibrary\ResponsiveImages\WidthCalculator\FileSizeOptimizedWidthCalculator::class, + + /* + * By default rendering media to a responsive image will add some javascript and a tiny placeholder. + * This ensures that the browser can already determine the correct layout. + * When disabled, no tiny placeholder is generated. + */ + 'use_tiny_placeholders' => true, + + /* + * This class will generate the tiny placeholder used for progressive image loading. By default + * the media library will use a tiny blurred jpg image. + */ + 'tiny_placeholder_generator' => Spatie\MediaLibrary\ResponsiveImages\TinyPlaceholderGenerator\Blurred::class, + ], + + /* + * When enabling this option, a route will be registered that will enable + * the Media Library Pro Vue and React components to move uploaded files + * in a S3 bucket to their right place. + */ + 'enable_vapor_uploads' => env('ENABLE_MEDIA_LIBRARY_VAPOR_UPLOADS', false), + + /* + * When converting Media instances to response the media library will add + * a `loading` attribute to the `img` tag. Here you can set the default + * value of that attribute. + * + * Possible values: 'lazy', 'eager', 'auto' or null if you don't want to set any loading instruction. + * + * More info: https://css-tricks.com/native-lazy-loading/ + */ + 'default_loading_attribute_value' => null, + + /* + * You can specify a prefix for that is used for storing all media. + * If you set this to `/my-subdir`, all your media will be stored in a `/my-subdir` directory. + */ + 'prefix' => env('MEDIA_PREFIX', ''), + + /* + * When forcing lazy loading, media will be loaded even if you don't eager load media and you have + * disabled lazy loading globally in the service provider. + */ + 'force_lazy_loading' => env('FORCE_MEDIA_LIBRARY_LAZY_LOADING', true), +]; diff --git a/application/config/permission.php b/application/config/permission.php new file mode 100644 index 0000000000000000000000000000000000000000..2a520f351290b9df0faeb25c58c8e190bde5d02c --- /dev/null +++ b/application/config/permission.php @@ -0,0 +1,186 @@ +<?php + +return [ + + 'models' => [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => Spatie\Permission\Models\Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => Spatie\Permission\Models\Role::class, + + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, //default 'role_id', + 'permission_pivot_key' => null, //default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered + * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated + * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. + */ + 'register_octane_reset_listener' => false, + + /* + * Teams Feature. + * When set to true the package implements teams using the 'team_foreign_key'. + * If you want the migrations to register the 'team_foreign_key', you must + * set this to true before doing the migration. + * If you already did the migration then you must make a new migration to also + * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions' + * (view the latest version of this package's migration file) + */ + + 'teams' => false, + + /* + * Passport Client Credentials Grant + * When set to true the package will use Passports Client to check permissions + */ + + 'use_passport_client_credentials' => false, + + /* + * When set to true, the required permission names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + * See documentation to understand supported syntax. + */ + + 'enable_wildcard_permission' => false, + + /* + * The class to use for interpreting wildcard permissions. + * If you need to modify delimiters, override the class and specify its name here. + */ + // 'permission.wildcard_permission' => Spatie\Permission\WildcardPermission::class, + + /* Cache-specific settings */ + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => \DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/application/database/migrations/2024_11_06_182434_create_permission_tables.php b/application/database/migrations/2024_11_06_182434_create_permission_tables.php new file mode 100644 index 0000000000000000000000000000000000000000..9c7044b46e1e838f5bf6cc9ae0ed41343c673c0d --- /dev/null +++ b/application/database/migrations/2024_11_06_182434_create_permission_tables.php @@ -0,0 +1,140 @@ +<?php + +use Illuminate\Support\Facades\Schema; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Migrations\Migration; + +return new class extends Migration +{ + /** + * Run the migrations. + */ + public function up(): void + { + $teams = config('permission.teams'); + $tableNames = config('permission.table_names'); + $columnNames = config('permission.column_names'); + $pivotRole = $columnNames['role_pivot_key'] ?? 'role_id'; + $pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id'; + + if (empty($tableNames)) { + throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.'); + } + if ($teams && empty($columnNames['team_foreign_key'] ?? null)) { + throw new \Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.'); + } + + Schema::create($tableNames['permissions'], function (Blueprint $table) { + //$table->engine('InnoDB'); + $table->bigIncrements('id'); // permission id + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { + //$table->engine('InnoDB'); + $table->bigIncrements('id'); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + $table->unsignedBigInteger($pivotPermission); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + + }); + + Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + $table->unsignedBigInteger($pivotRole); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $tableNames = config('permission.table_names'); + + if (empty($tableNames)) { + throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + } + + Schema::drop($tableNames['role_has_permissions']); + Schema::drop($tableNames['model_has_roles']); + Schema::drop($tableNames['model_has_permissions']); + Schema::drop($tableNames['roles']); + Schema::drop($tableNames['permissions']); + } +}; diff --git a/application/database/migrations/2024_11_07_042703_create_media_table.php b/application/database/migrations/2024_11_07_042703_create_media_table.php new file mode 100644 index 0000000000000000000000000000000000000000..47a4be987d7bd92ad89a2ea2750baff2d6302010 --- /dev/null +++ b/application/database/migrations/2024_11_07_042703_create_media_table.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + public function up(): void + { + Schema::create('media', function (Blueprint $table) { + $table->id(); + + $table->morphs('model'); + $table->uuid()->nullable()->unique(); + $table->string('collection_name'); + $table->string('name'); + $table->string('file_name'); + $table->string('mime_type')->nullable(); + $table->string('disk'); + $table->string('conversions_disk')->nullable(); + $table->unsignedBigInteger('size'); + $table->json('manipulations'); + $table->json('custom_properties'); + $table->json('generated_conversions'); + $table->json('responsive_images'); + $table->unsignedInteger('order_column')->nullable()->index(); + + $table->nullableTimestamps(); + }); + } +}; diff --git a/application/database/seeders/DatabaseSeeder.php b/application/database/seeders/DatabaseSeeder.php index d158806bb7a6d44db3dbabdfea14ef26347258d0..7624e1b99ee6d8bd794f5f7e19bad2f377ea8837 100644 --- a/application/database/seeders/DatabaseSeeder.php +++ b/application/database/seeders/DatabaseSeeder.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Database\Seeders; use App\Models\User; diff --git a/application/database/seeders/RoleSeeder.php b/application/database/seeders/RoleSeeder.php new file mode 100644 index 0000000000000000000000000000000000000000..2f0bde033bc1eb53013974533de856eeb322ad95 --- /dev/null +++ b/application/database/seeders/RoleSeeder.php @@ -0,0 +1,24 @@ +<?php + +declare(strict_types=1); + +namespace Database\Seeders; + +use App\Enums\RoleEnum; +use Illuminate\Database\Seeder; +use Spatie\Permission\Models\Role; +use Illuminate\Database\Console\Seeds\WithoutModelEvents; + +class RoleSeeder extends Seeder +{ + /** + * Run the database seeds. + */ + public function run(): void + { + collect(array_keys(RoleEnum::toArray())) + ->each( + fn($role) => Role::findOrCreate($role) + ); + } +} diff --git a/application/database/seeders/Traits/GetImageLink.php b/application/database/seeders/Traits/GetImageLink.php new file mode 100644 index 0000000000000000000000000000000000000000..16d61ab482a03ac87d801f923418bd1da7b08721 --- /dev/null +++ b/application/database/seeders/Traits/GetImageLink.php @@ -0,0 +1,22 @@ +<?php + +declare(strict_types=1); + +namespace Database\Seeders\Traits; + +trait GetImageLink +{ + /** @throws \Exception */ + public function getRandomImageLink(int $width = 640, int $height = 480): null|string + { + $url = "https://picsum.photos/{$width}/{$height}"; + + $headers = get_headers($url, true); + + if ($headers && isset($headers['Content-Type']) && str_contains($headers['Content-Type'], 'image')) { + return $url; + } + + return null; + } +} diff --git a/application/database/seeders/UserSeeder.php b/application/database/seeders/UserSeeder.php index a4dfc1e4b5cba4b3c29c06b299d8e50339c4cb29..56019031ae9e4ef4f212a4f076fe2416614de0a9 100644 --- a/application/database/seeders/UserSeeder.php +++ b/application/database/seeders/UserSeeder.php @@ -1,22 +1,39 @@ <?php +declare(strict_types=1); + namespace Database\Seeders; use App\Models\User; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; +use App\Enums\RoleEnum; use Illuminate\Database\Seeder; +use Spatie\Permission\Models\Role; +use Illuminate\Support\Facades\Hash; +use Database\Seeders\Traits\GetImageLink; +use Illuminate\Database\Console\Seeds\WithoutModelEvents; class UserSeeder extends Seeder { + use GetImageLink; + /** * Run the database seeds. */ public function run(): void { - User::factory()->count(10)->create(); - User::factory()->create([ - 'name' => 'Test User', - 'email' => 'test@example.com', - ]); + User::factory([ + 'name' => 'Explorer Dora', + 'email' => 'admin@catalystexplorer.com', + 'password' => Hash::make('ofnXIFbZ0JOuGBqx-'), + ])->hasAttached(Role::where('name', RoleEnum::super_admin())->first()) + ->create(); + + User::factory(7)->create()->each( + function (User $user) { + if ($imageLink = $this->getRandomImageLink()) { + $user->addMediaFromUrl($imageLink)->toMediaCollection('profile'); + } + } + ); } } diff --git a/application/package.json b/application/package.json index bb664be34c2f27738c1a3aa9666c1496d35facda..5463c99368299f50f8a82f9afde7b425346e9209 100644 --- a/application/package.json +++ b/application/package.json @@ -28,7 +28,7 @@ "postcss": "^8.4.31", "prettier": "^3.3.0", "prettier-plugin-organize-imports": "^4.0.0", - "prettier-plugin-tailwindcss": "^0.6.5", + "prettier-plugin-tailwindcss": "^0.6.8", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.80.5", diff --git a/application/resources/js/Components/Dropdown.tsx b/application/resources/js/Components/Dropdown.tsx index 105564220134826ee3f1908b102be84603cc97b0..29207040df8391c3f50d2db6811dc33cfaaf46de 100644 --- a/application/resources/js/Components/Dropdown.tsx +++ b/application/resources/js/Components/Dropdown.tsx @@ -53,7 +53,7 @@ const Trigger = ({ children }: PropsWithChildren) => { const Content = ({ align = 'right', width = '48', - contentClasses = 'py-1 bg-white dark:bg-gray-700', + contentClasses = 'py-1 bg-background-primary', children, }: PropsWithChildren<{ align?: 'left' | 'right'; @@ -114,7 +114,7 @@ const DropdownLink = ({ <Link {...props} className={ - 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 transition duration-150 ease-in-out hover:bg-gray-100 focus:bg-gray-100 focus:outline-none dark:text-gray-300 dark:hover:bg-gray-800 dark:focus:bg-gray-800 ' + + 'block w-full px-4 py-2 text-start text-sm leading-5 text-content-primary transition duration-150 ease-in-out hover:bg-background-secondary focus:outline-none focus:bg-background-secondary ' + className } > diff --git a/application/resources/js/Components/InputLabel.tsx b/application/resources/js/Components/InputLabel.tsx index c51c0161b80e2856fb0e34c4b7250ed74707ddc7..b83c1134c16f4dc70df2f6d358b52ba7ec7b0b16 100644 --- a/application/resources/js/Components/InputLabel.tsx +++ b/application/resources/js/Components/InputLabel.tsx @@ -10,8 +10,7 @@ export default function InputLabel({ <label {...props} className={ - `block text-sm font-medium text-gray-700 dark:text-gray-300 ` + - className + `block text-sm font-medium text-content-tertiary ` + className } > {value ? value : children} diff --git a/application/resources/js/Components/NavLink.tsx b/application/resources/js/Components/NavLink.tsx index a756359424e5ced2ba3877584aa8117e5dd7f0b1..e138dc96139d13aa8abe4d01502f503191ead988 100644 --- a/application/resources/js/Components/NavLink.tsx +++ b/application/resources/js/Components/NavLink.tsx @@ -10,10 +10,10 @@ export default function NavLink({ <Link {...props} className={ - 'inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium leading-5 transition duration-150 ease-in-out focus:outline-none ' + + 'inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium leading-5 text-content-primary transition duration-150 ease-in-out focus:outline-none ' + (active - ? 'border-indigo-400 text-gray-900 focus:border-indigo-700 dark:border-indigo-600 dark:text-gray-100' - : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 focus:border-gray-300 focus:text-gray-700 dark:text-gray-400 dark:hover:border-gray-700 dark:hover:text-gray-300 dark:focus:border-gray-700 dark:focus:text-gray-300') + + ? 'border-gray-200 focus:border-indigo-700 dark:border-indigo-600' + : 'border-transparent hover:border-gray-300 hover:text-gray-700 focus:border-gray-300 focus:text-gray-700 dark:text-gray-400 dark:hover:border-gray-700') + className } > diff --git a/application/resources/js/Components/PrimaryButton.tsx b/application/resources/js/Components/PrimaryButton.tsx index 479897426191a02a55305c2f0dab2cb2d2d614e2..d6b57038e6807a3f9858564729e928dfe0d724e2 100644 --- a/application/resources/js/Components/PrimaryButton.tsx +++ b/application/resources/js/Components/PrimaryButton.tsx @@ -10,7 +10,7 @@ export default function PrimaryButton({ <button {...props} className={ - `inline-flex items-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-xs font-semibold uppercase tracking-widest text-white transition duration-150 ease-in-out hover:bg-gray-700 focus:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 active:bg-gray-900 dark:bg-gray-200 dark:text-gray-800 dark:hover:bg-white dark:focus:bg-white dark:focus:ring-offset-gray-800 dark:active:bg-gray-300 ${ + `inline-flex items-center rounded-md border border-transparent bg-background-primary px-4 py-2 text-xs font-semibold uppercase tracking-widest text-white transition duration-150 ease-in-out hover:bg-gray-700 focus:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 active:bg-gray-900 dark:bg-gray-200 dark:text-gray-800 dark:hover:bg-white dark:focus:bg-white dark:focus:ring-offset-gray-800 dark:active:bg-gray-300 ${ disabled && 'opacity-25' } ` + className } diff --git a/application/resources/js/Components/SecondaryButton.tsx b/application/resources/js/Components/SecondaryButton.tsx index 4ad249dd6a99fedbef20cdb687e30799061e8d59..d4f62d1b638be80587d84ed1950beae0438a9de1 100644 --- a/application/resources/js/Components/SecondaryButton.tsx +++ b/application/resources/js/Components/SecondaryButton.tsx @@ -12,7 +12,7 @@ export default function SecondaryButton({ {...props} type={type} className={ - `inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-xs font-semibold uppercase tracking-widest text-gray-700 shadow-sm transition duration-150 ease-in-out hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 dark:border-gray-500 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 dark:focus:ring-offset-gray-800 ${ + `inline-flex items-center rounded-md border border-gray-300 bg-background-secondary px-4 py-2 text-xs font-semibold uppercase tracking-widest text-gray-700 shadow-sm transition duration-150 ease-in-out hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 dark:border-gray-500 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700 dark:focus:ring-offset-gray-800 ${ disabled && 'opacity-25' } ` + className } diff --git a/application/resources/js/Components/TextInput.tsx b/application/resources/js/Components/TextInput.tsx index 2dd01037eb58278076e979d87a430fef6afcd357..93ede93727e24f4561e8130b76fabf2705681a9e 100644 --- a/application/resources/js/Components/TextInput.tsx +++ b/application/resources/js/Components/TextInput.tsx @@ -32,7 +32,7 @@ export default forwardRef(function TextInput( {...props} type={type} className={ - 'rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:focus:border-indigo-600 dark:focus:ring-indigo-600 ' + + 'bg-primary rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-700 dark:text-gray-300 dark:focus:border-indigo-600 dark:focus:ring-indigo-600 ' + className } ref={localRef} diff --git a/application/resources/js/Components/layout/DesktopSidebar.tsx b/application/resources/js/Components/layout/DesktopSidebar.tsx index 8f8628178cdabe864137b1f93f63466037c9fe94..4be3702a82c5bc6bc1fc4a049d0a28498097ed8c 100644 --- a/application/resources/js/Components/layout/DesktopSidebar.tsx +++ b/application/resources/js/Components/layout/DesktopSidebar.tsx @@ -5,6 +5,7 @@ import AppNavigation from './AppNavigation'; import ThemeSwitcher from './ThemeSwitcher'; import UserDetails from './UserDetails'; import UserNavigation from './UserNavigation'; +import User = App.DataTransferObjects.UserData; function DesktopSidebar() { @@ -25,7 +26,7 @@ function DesktopSidebar() { <section className="flex flex-col gap-6 px-4 pb-8"> <ThemeSwitcher /> <UserNavigation /> - <UserDetails name={auth?.user?.name} email={auth?.user?.email} avatar={auth?.avatar}/> + <UserDetails user={auth?.user as User} /> </section> </aside> ); diff --git a/application/resources/js/Components/layout/MobileNavigation.tsx b/application/resources/js/Components/layout/MobileNavigation.tsx index 5b61bb6766d7907b7fcab37058da47d48f4b87fc..42e69b9f9e0f164343d7b509104f074ca5c0fdfe 100644 --- a/application/resources/js/Components/layout/MobileNavigation.tsx +++ b/application/resources/js/Components/layout/MobileNavigation.tsx @@ -5,6 +5,7 @@ import ThemeSwitcher from './ThemeSwitcher'; import UserDetails from './UserDetails'; import UserNavigation from './UserNavigation'; import { usePage } from '@inertiajs/react'; +import User = App.DataTransferObjects.UserData; function MobileNavigation() { const { t } = useTranslation(); @@ -26,7 +27,7 @@ function MobileNavigation() { <section className="flex flex-col gap-6 px-4 pb-8"> <ThemeSwitcher /> <UserNavigation /> - <UserDetails name={auth?.user?.name} email={auth?.user?.email} avatar={auth?.avatar}/> + <UserDetails user={auth.user as User} /> </section> </aside> </DialogPanel> diff --git a/application/resources/js/Components/layout/UserDetails.tsx b/application/resources/js/Components/layout/UserDetails.tsx index 32009b98b4b78d3e61605f9862708ecde7b08cc9..904bcf25aea471fbb4ed9d19f90be50a1c5a29cf 100644 --- a/application/resources/js/Components/layout/UserDetails.tsx +++ b/application/resources/js/Components/layout/UserDetails.tsx @@ -1,59 +1,79 @@ -import { useTranslation } from 'react-i18next'; -import LogOutIcon from '../svgs/LogOut'; import { Link } from '@inertiajs/react'; import axios from 'axios'; +import { useTranslation } from 'react-i18next'; import { route } from '../../../../vendor/tightenco/ziggy/src/js'; +import LogOutIcon from '../svgs/LogOut'; +import User = App.DataTransferObjects.UserData; -export interface UserDetailsProps { - name: string, - email: string, - avatar: string +interface UserDetailsProps { + user: App.DataTransferObjects.UserData; } -const UserDetails: React.FC<UserDetailsProps> = ({ name, email, avatar }) => { +const UserDetails: React.FC<UserDetailsProps> = ({user}) => { const { t } = useTranslation(); const logout = () => { - axios.post(route('logout')).then((response) => { - console.log(response) - window.location.href = '/' - }).catch((error) => { - console.log(error) - }) - } + axios + .post(route('logout')) + .then((response) => { + console.log(response); + window.location.href = '/'; + }) + .catch((error) => { + console.log(error); + }); + }; return ( <div className="flex items-center justify-between border-t border-gray-200 pt-6"> <div className="flex gap-3"> - <div className="h-9 w-9 rounded-full bg-gray-400"> - {name && email ? <img src={avatar} alt='avatar' /> : ''} + <div className="size-9 rounded-full bg-gray-400"> + {user?.name && user?.email ? ( + <img + src={user.profile_photo_url} + alt="avatar" + className="size-9 rounded-full" + /> + ) : ( + '' + )} </div> <div className="flex flex-col"> - {name && email ? ( - <Link href='/dashboard' className="text-sm font-semibold text-content-primary"> - {name} + {user?.name && user?.email ? ( + <Link + href="/dashboard" + className="text-sm font-semibold text-content-primary" + > + {user?.name} </Link> - ) : <p className="text-sm font-semibold text-content-primary">{t('app.name')}</p>} + ) : ( + <p className="text-sm font-semibold text-content-primary"> + {t('app.name')} + </p> + )} <p className="text-xs text-content-primary"> - {email || t('app.contactEmail')} + {user?.email || t('app.contactEmail')} </p> - {name && email && ( - <Link href='/profile' className="text-xs font-semibold text-primary-100"> + {user?.name && user?.email && ( + <Link + href="/profile" + className="text-xs font-semibold text-primary-100" + > Edit profile </Link> )} </div> </div> <LogOutIcon - className="text-content-tertiary cursor-pointer" + className="cursor-pointer text-content-tertiary" width={20} height={20} - onClick={()=>logout()} + onClick={() => logout()} /> </div> ); -} +}; export default UserDetails; diff --git a/application/resources/js/Layouts/AuthenticatedLayout.tsx b/application/resources/js/Layouts/AuthenticatedLayout.tsx index cb449b2a918929e479b313fda9b0d7b09834cfc9..7ada531add19e56593e8d693fc6db98a2e1c20d8 100644 --- a/application/resources/js/Layouts/AuthenticatedLayout.tsx +++ b/application/resources/js/Layouts/AuthenticatedLayout.tsx @@ -15,14 +15,14 @@ export default function Authenticated({ useState(false); return ( - <div className="min-h-screen bg-gray-100 dark:bg-gray-900"> - <nav className="border-b border-gray-100 bg-white dark:border-gray-700 dark:bg-gray-800"> + <div className="bg-gray-primary min-h-screen"> + <nav className="border-b border-gray-100 bg-background-primary"> <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> <div className="flex h-16 justify-between"> <div className="flex"> <div className="flex shrink-0 items-center"> <Link href="/"> - <ApplicationLogo className="block h-9 w-auto fill-current text-gray-800 dark:text-gray-200" /> + <ApplicationLogo className="block h-9 w-auto fill-current text-content-primary" /> </Link> </div> @@ -43,7 +43,7 @@ export default function Authenticated({ <span className="inline-flex rounded-md"> <button type="button" - className="inline-flex items-center rounded-md border border-transparent bg-white px-3 py-2 text-sm font-medium leading-4 text-gray-500 transition duration-150 ease-in-out hover:text-gray-700 focus:outline-none dark:bg-gray-800 dark:text-gray-400 dark:hover:text-gray-300" + className="false inline-flex items-center rounded-md border border-transparent bg-background-secondary px-4 py-2 text-xs font-semibold uppercase tracking-widest text-white transition duration-150 ease-in-out hover:bg-gray-700 focus:bg-gray-700 focus:outline-none focus:ring-offset-2 active:bg-gray-900 dark:bg-gray-200 dark:text-gray-800 dark:hover:bg-white dark:focus:bg-white dark:focus:ring-offset-gray-800 dark:active:bg-gray-300" > {user.name} @@ -139,9 +139,9 @@ export default function Authenticated({ </ResponsiveNavLink> </div> - <div className="border-t border-gray-200 pb-1 pt-4 dark:border-gray-600"> + <div className="border-t border-gray-200 pb-1 pt-4"> <div className="px-4"> - <div className="text-base font-medium text-gray-800 dark:text-gray-200"> + <div className="text-base font-medium text-gray-800"> {user.name} </div> <div className="text-sm font-medium text-gray-500"> @@ -166,7 +166,7 @@ export default function Authenticated({ </nav> {header && ( - <header className="bg-white shadow dark:bg-gray-800"> + <header className="bg-background-primary shadow"> <div className="mx-auto max-w-7xl px-4 py-6 sm:px-6 lg:px-8"> {header} </div> diff --git a/application/resources/js/Pages/Dashboard.tsx b/application/resources/js/Pages/Profile/Dashboard.tsx similarity index 75% rename from application/resources/js/Pages/Dashboard.tsx rename to application/resources/js/Pages/Profile/Dashboard.tsx index 5ff157f8ff0437e3ed145af282d7a759a7dac4cc..909028b9feb4af8e269fc240c1f61d4137af0440 100644 --- a/application/resources/js/Pages/Dashboard.tsx +++ b/application/resources/js/Pages/Profile/Dashboard.tsx @@ -5,7 +5,7 @@ export default function Dashboard() { return ( <AuthenticatedLayout header={ - <h2 className="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200"> + <h2 className="text-xl font-semibold leading-tight text-content-primary"> Dashboard </h2> } @@ -14,8 +14,8 @@ export default function Dashboard() { <div className="py-12"> <div className="mx-auto max-w-7xl sm:px-6 lg:px-8"> - <div className="overflow-hidden bg-white shadow-sm sm:rounded-lg dark:bg-[#112A3B]"> - <div className="p-6 text-gray-900 dark:text-gray-100"> + <div className="overflow-hidden bg-background-primary shadow-sm sm:rounded-lg"> + <div className="p-6 text-content-primary"> You're logged in! </div> </div> diff --git a/application/resources/js/Pages/Profile/Edit.tsx b/application/resources/js/Pages/Profile/Edit.tsx index eeda6110f74e2bdf5e12ea4c3b308a75217d9db6..6bc4b1dfce4eba7f378c8ef714314b689d9c26e0 100644 --- a/application/resources/js/Pages/Profile/Edit.tsx +++ b/application/resources/js/Pages/Profile/Edit.tsx @@ -12,7 +12,7 @@ export default function Edit({ return ( <AuthenticatedLayout header={ - <h2 className="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200"> + <h2 className="text-xl font-semibold leading-tight text-content-primary"> Profile </h2> } @@ -21,7 +21,7 @@ export default function Edit({ <div className="py-12"> <div className="mx-auto max-w-7xl space-y-6 sm:px-6 lg:px-8"> - <div className="bg-white p-4 shadow sm:rounded-lg sm:p-8 dark:bg-gray-800"> + <div className="bg-background-primary p-4 shadow sm:rounded-lg sm:p-8"> <UpdateProfileInformationForm mustVerifyEmail={mustVerifyEmail} status={status} @@ -29,11 +29,11 @@ export default function Edit({ /> </div> - <div className="bg-white p-4 shadow sm:rounded-lg sm:p-8 dark:bg-gray-800"> + <div className="bg-background-primary p-4 shadow sm:rounded-lg sm:p-8"> <UpdatePasswordForm className="max-w-xl" /> </div> - <div className="bg-white p-4 shadow sm:rounded-lg sm:p-8 dark:bg-gray-800"> + <div className="bg-background-primary p-4 shadow sm:rounded-lg sm:p-8"> <DeleteUserForm className="max-w-xl" /> </div> </div> diff --git a/application/resources/js/Pages/Profile/Partials/DeleteUserForm.tsx b/application/resources/js/Pages/Profile/Partials/DeleteUserForm.tsx index 33ab4c95f7e8594a6daba6a946eba954274d604a..540150b1870beeee439e50a5ffea32c463cd6004 100644 --- a/application/resources/js/Pages/Profile/Partials/DeleteUserForm.tsx +++ b/application/resources/js/Pages/Profile/Partials/DeleteUserForm.tsx @@ -52,11 +52,11 @@ export default function DeleteUserForm({ return ( <section className={`space-y-6 ${className}`}> <header> - <h2 className="text-lg font-medium text-gray-900 dark:text-gray-100"> + <h2 className="text-lg font-medium text-content-primary"> Delete Account </h2> - <p className="mt-1 text-sm text-gray-600 dark:text-gray-400"> + <p className="mt-1 text-sm text-content-primary"> Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to diff --git a/application/resources/js/Pages/Profile/Partials/UpdatePasswordForm.tsx b/application/resources/js/Pages/Profile/Partials/UpdatePasswordForm.tsx index 585c2ff69339cdcb1b7f9300dd0d310ba0c4d097..8172781f889f5b3c5df2c6706629dd7bd539825e 100644 --- a/application/resources/js/Pages/Profile/Partials/UpdatePasswordForm.tsx +++ b/application/resources/js/Pages/Profile/Partials/UpdatePasswordForm.tsx @@ -51,11 +51,11 @@ export default function UpdatePasswordForm({ return ( <section className={className}> <header> - <h2 className="text-lg font-medium text-gray-900 dark:text-gray-100"> + <h2 className="text-lg font-medium text-content-primary"> Update Password </h2> - <p className="mt-1 text-sm text-gray-600 dark:text-gray-400"> + <p className="mt-1 text-sm text-content-primary"> Ensure your account is using a long, random password to stay secure. </p> diff --git a/application/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.tsx b/application/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.tsx index ea1751fb3f57f086ad5c5b8e907e823434fc3aba..9f71db3d55da9ffec930448bbffe031c13aef941 100644 --- a/application/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.tsx +++ b/application/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.tsx @@ -32,11 +32,11 @@ export default function UpdateProfileInformation({ return ( <section className={className}> <header> - <h2 className="text-lg font-medium text-gray-900 dark:text-gray-100"> + <h2 className="text-lg font-medium text-content-primary"> Profile Information </h2> - <p className="mt-1 text-sm text-gray-600 dark:text-gray-400"> + <p className="mt-1 text-sm text-content-primary"> Update your account's profile information and email address. </p> </header> @@ -76,13 +76,13 @@ export default function UpdateProfileInformation({ {mustVerifyEmail && user.email_verified_at === null && ( <div> - <p className="mt-2 text-sm text-gray-800 dark:text-gray-200"> + <p className="mt-2 text-sm text-content-primary"> Your email address is unverified. <Link href={route('verification.send')} method="post" as="button" - className="rounded-md text-sm text-gray-600 underline hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:text-gray-400 dark:hover:text-gray-100 dark:focus:ring-offset-gray-800" + className="rounded-md text-sm text-content-primary underline hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:text-gray-400 dark:hover:text-gray-100 dark:focus:ring-offset-gray-800" > Click here to re-send the verification email. </Link> diff --git a/application/resources/js/app.tsx b/application/resources/js/app.tsx index c96ed67e4e94f016485ac28415b4e30ec180903e..6c70f0dd798767cb9b2202805a4ed5cd85b967c2 100644 --- a/application/resources/js/app.tsx +++ b/application/resources/js/app.tsx @@ -1,9 +1,10 @@ import '../scss/app.scss'; import './bootstrap'; -import './utils/i18n' +import './utils/i18n'; import { createInertiaApp } from '@inertiajs/react'; import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; +import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import AppLayout from './Layouts/AppLayout'; @@ -14,17 +15,23 @@ createInertiaApp({ resolve: (name) => { const page = resolvePageComponent( `./Pages/${name}.tsx`, - import.meta.glob("./Pages/**/*.tsx") + import.meta.glob('./Pages/**/*.tsx'), ); page.then((module: any) => { - module.default.layout = module.default.layout || ((module: any) => <AppLayout children={module}/>); + module.default.layout = + module.default.layout || + ((module: any) => <AppLayout children={module} />); }); return page; }, - setup({el, App, props}) { + setup({ el, App, props }) { const root = createRoot(el); - root.render(<App {...props} />); + root.render( + <StrictMode> + <App {...props} /> + </StrictMode>, + ); }, progress: { color: '#4B5563', diff --git a/application/resources/js/types/index.d.ts b/application/resources/js/types/index.d.ts index aa704cba0f5664802ded79812e9f4aa17cdd18f9..ef849bce4ff569e7de02bcf9341f8823bde328ad 100644 --- a/application/resources/js/types/index.d.ts +++ b/application/resources/js/types/index.d.ts @@ -10,6 +10,5 @@ export type PageProps< > = T & { auth: { user: User; - avatar: string; }; }; diff --git a/application/resources/types/generated.d.ts b/application/resources/types/generated.d.ts index 03e10d9c72c6e19f5b1ecb86f6506df590487b21..9ffa96fc5c21701ae49cee0ae88516ffea34eef5 100644 --- a/application/resources/types/generated.d.ts +++ b/application/resources/types/generated.d.ts @@ -36,4 +36,11 @@ ranking_total?: number; quickpitch?: string; quickpitch_length?: number; }; +export type UserData = { +id: number; +name: string; +email: string; +profile_photo_url: string; +email_verified_at: string; +}; } diff --git a/application/routes/auth.php b/application/routes/auth.php index aa8292106cb7c24decc91857201737ed650ea7e5..0b9fbf95da08cbed73a93870dc3f9278c1026997 100644 --- a/application/routes/auth.php +++ b/application/routes/auth.php @@ -1,16 +1,17 @@ <?php -use App\Http\Controllers\Auth\AuthenticatedSessionController; -use App\Http\Controllers\Auth\ConfirmablePasswordController; -use App\Http\Controllers\Auth\EmailVerificationNotificationController; -use App\Http\Controllers\Auth\EmailVerificationPromptController; -use App\Http\Controllers\Auth\NewPasswordController; +use Illuminate\Support\Facades\Route; +use App\Http\Controllers\ProfileController; use App\Http\Controllers\Auth\PasswordController; -use App\Http\Controllers\Auth\PasswordResetLinkController; -use App\Http\Controllers\Auth\RegisteredUserController; +use App\Http\Controllers\Auth\NewPasswordController; use App\Http\Controllers\Auth\VerifyEmailController; -use App\Http\Controllers\ProfileController; -use Illuminate\Support\Facades\Route; +use App\Http\Controllers\Auth\RegisteredUserController; +use App\Http\Controllers\Auth\PasswordResetLinkController; +use App\Http\Controllers\Auth\ConfirmablePasswordController; +use App\Http\Controllers\Auth\AuthenticatedSessionController; +use App\Http\Controllers\Auth\EmailVerificationPromptController; +use App\Http\Controllers\Auth\EmailVerificationNotificationController; + Route::middleware('auth')->group(function () { Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); diff --git a/application/routes/dashboard.php b/application/routes/dashboard.php index 55d80fe389452bf8b5f32ae903364608242a0aba..1a92bd999397bfd7de6582702d8fe1ad60bd318f 100644 --- a/application/routes/dashboard.php +++ b/application/routes/dashboard.php @@ -4,6 +4,6 @@ use Inertia\Inertia; Route::get('/dashboard', function () { - return Inertia::render('Dashboard'); + return Inertia::render('Profile/Dashboard'); })->middleware(['auth', 'verified']) ->name('dashboard'); diff --git a/application/storage/app/.gitignore b/application/storage/app/.gitignore old mode 100644 new mode 100755 diff --git a/application/storage/app/private/.gitignore b/application/storage/app/private/.gitignore old mode 100644 new mode 100755 diff --git a/application/storage/app/public/.gitignore b/application/storage/app/public/.gitignore old mode 100644 new mode 100755 diff --git a/application/storage/framework/.gitignore b/application/storage/framework/.gitignore old mode 100644 new mode 100755 diff --git a/application/storage/framework/cache/.gitignore b/application/storage/framework/cache/.gitignore old mode 100644 new mode 100755 diff --git a/application/storage/framework/cache/data/.gitignore b/application/storage/framework/cache/data/.gitignore old mode 100644 new mode 100755 diff --git a/application/storage/framework/sessions/.gitignore b/application/storage/framework/sessions/.gitignore old mode 100644 new mode 100755 diff --git a/application/storage/framework/testing/.gitignore b/application/storage/framework/testing/.gitignore old mode 100644 new mode 100755 diff --git a/application/storage/framework/views/.gitignore b/application/storage/framework/views/.gitignore old mode 100644 new mode 100755 diff --git a/application/storage/logs/.gitignore b/application/storage/logs/.gitignore old mode 100644 new mode 100755 diff --git a/application/tsconfig.json b/application/tsconfig.json index 37071140a8b87ebd9e4a4bc85df5867fcfd0b956..83b904b36eeda19d4ec2352cdc4d03ac93e62a5f 100644 --- a/application/tsconfig.json +++ b/application/tsconfig.json @@ -12,9 +12,23 @@ "skipLibCheck": true, "noEmit": true, "paths": { - "@/*": ["./resources/js/*"], - "ziggy-js": ["./vendor/tightenco/ziggy"] - } + "@/*": [ + "./resources/js/*" + ], + "ziggy-js": [ + "./vendor/tightenco/ziggy" + ] + }, + "typeRoots": [ + "./node_modules/@types", + "resources/js/types", + "resources/types" + ], }, - "include": ["resources/js/**/*.ts", "resources/js/**/*.tsx", "resources/js/**/*.d.ts"] -} + "include": [ + "resources/js/**/*.ts", + "resources/js/**/*.tsx", + "resources/js/**/*.d.ts", + "resources/types/*.d.ts" + ] +} \ No newline at end of file diff --git a/application/yarn.lock b/application/yarn.lock index ec29045a4f6a9270b0a722fdaea23b9156ea262f..cb5c88164cc9ea6b76cc03552f020d6eeb0d43e7 100644 --- a/application/yarn.lock +++ b/application/yarn.lock @@ -2656,7 +2656,7 @@ prettier-plugin-organize-imports@^4.0.0: resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz#f3d3764046a8e7ba6491431158b9be6ffd83b90f" integrity sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A== -prettier-plugin-tailwindcss@^0.6.5: +prettier-plugin-tailwindcss@^0.6.8: version "0.6.8" resolved "https://registry.yarnpkg.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.8.tgz#8a178e1679e3f941cc9de396f109c6cffea676d8" integrity sha512-dGu3kdm7SXPkiW4nzeWKCl3uoImdd5CTZEJGxyypEPL37Wj0HT2pLqjrvSei1nTeuQfO4PUfjeW5cTUNRLZ4sA== diff --git a/docker/Dockerfile.base b/docker/Dockerfile.base index 01d3ff5e77ca66cf226970c8b8d7e1583dd9d6ee..e053ff4d22e405c3dbafe7fab6b85eb761f37b13 100644 --- a/docker/Dockerfile.base +++ b/docker/Dockerfile.base @@ -6,6 +6,8 @@ ARG WWWGROUP ARG NODE_VERSION=20 ARG MYSQL_CLIENT="mysql-client" ARG POSTGRES_VERSION=15 +ARG FRANKENPHP_VERSION=1.2.5 +ARG FRANKENPHP_ARCH=linux-aarch64 WORKDIR /var/www/html @@ -54,9 +56,12 @@ RUN apt-get update && apt-get upgrade -y \ && apt-get -y autoremove \ && apt-get clean \ && apt-get install -y netcat \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - - + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ + && curl -L# "https://github.com/dunglas/frankenphp/releases/download/v${FRANKENPHP_VERSION}/frankenphp-${FRANKENPHP_ARCH}" -o /var/wwwfrankenphp \ + && chmod +x /var/wwwfrankenphp \ + && apt-get install jpegoptim optipng pngquant gifsicle libavif-bin\ + && npm install -g svgo + RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.3 diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index 31f87073b8822cd2041385010cb87b5ce7e87ffe..115d5212a2d7d7bfc8bfbc31e1b96946a1c1d4e9 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -6,6 +6,8 @@ ARG WWWGROUP ARG NODE_VERSION=20 ARG MYSQL_CLIENT="mysql-client" ARG POSTGRES_VERSION=15 +ARG FRANKENPHP_VERSION=1.2.5 +ARG FRANKENPHP_ARCH=linux-aarch64 WORKDIR /var/www/html @@ -22,7 +24,7 @@ RUN echo "Acquire::http::Pipeline-Depth 0;" > /etc/apt/apt.conf.d/99custom && \ RUN apt-get update && apt-get upgrade -y \ && mkdir -p /etc/apt/keyrings \ - && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2 dnsutils librsvg2-bin fswatch ffmpeg nano \ + && apt-get install -y jpegoptim optipng pngquant gifsicle libavif-bin gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2 dnsutils librsvg2-bin fswatch ffmpeg nano \ && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \ && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ && apt-get update \ @@ -43,6 +45,7 @@ RUN apt-get update && apt-get upgrade -y \ && npm install -g npm \ && npm install -g pnpm \ && npm install -g bun \ + && npm install -g svgo \ && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \ && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \ @@ -53,7 +56,9 @@ RUN apt-get update && apt-get upgrade -y \ && apt-get install -y postgresql-client-$POSTGRES_VERSION \ && apt-get -y autoremove \ && apt-get clean \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + + RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.3 @@ -70,4 +75,5 @@ EXPOSE 8000 WORKDIR /var/www + ENTRYPOINT ["start-container"]