diff --git a/app/Models/User.php b/app/Models/User.php index ccb3e97..e7732fa 100755 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,24 +2,7 @@ namespace App\Models; -use Database\Factories\UserFactory; -use Filament\Models\Contracts\FilamentUser; -use Filament\Models\Contracts\HasAvatar; -use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Relations\HasOne; -use Illuminate\Database\Eloquent\SoftDeletes; -use Illuminate\Foundation\Auth\User as Authenticatable; -use Illuminate\Notifications\Notifiable; -use Jeffgreco13\FilamentBreezy\Traits\TwoFactorAuthenticatable; -use Laravel\Scout\Searchable; -use Packages\React\Traits\CanFollow; -use Packages\React\Traits\HasFollowers; -use Spatie\Activitylog\LogOptions; -use Spatie\Activitylog\Traits\LogsActivity; -use Spatie\MediaLibrary\HasMedia; -use Spatie\MediaLibrary\InteractsWithMedia; -use Spatie\MediaLibrary\MediaCollections\File; -use Spatie\Permission\Traits\HasRoles; +use Packages\User\Models\User as BaseUser; /** * @property int $id @@ -48,13 +31,13 @@ * @property-read int|null $notifications_count * @property-read \Illuminate\Database\Eloquent\Collection $permissions * @property-read int|null $permissions_count - * @property-read UserProfile|null $profile + * @property-read \Packages\User\Models\UserProfile|null $profile * @property-read \Illuminate\Database\Eloquent\Collection $roles * @property-read int|null $roles_count * @property-read mixed $two_factor_recovery_codes * @property-read mixed $two_factor_secret * - * @method static \Database\Factories\UserFactory factory($count = null, $state = []) + * @method static \Packages\User\Database\Factories\UserFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|User newQuery() * @method static \Illuminate\Database\Eloquent\Builder|User onlyTrashed() @@ -78,153 +61,6 @@ * * @mixin \Eloquent */ -class User extends Authenticatable implements FilamentUser, HasMedia, HasAvatar +class User extends BaseUser { - use CanFollow; - - /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory; - - use HasFollowers; - use HasRoles; - use InteractsWithMedia; - use LogsActivity; - use Notifiable; - use SoftDeletes; - use TwoFactorAuthenticatable; - use Searchable; - - /** - * The attributes that are mass assignable. - * - * @var list - */ - protected $fillable = [ - 'name', - 'email', - 'username', - 'password', - ]; - - /** - * The attributes that should be hidden for serialization. - * - * @var list - */ - protected $hidden = [ - 'password', - 'remember_token', - ]; - - /** - * @return HasOne - */ - public function profile(): HasOne - { - return $this->hasOne(UserProfile::class); - } - - public function getFilamentAvatarUrl(): ?string - { - return $this->getAvatarImage(); - } - - /** - * Get the avatar image of the user. - */ - public function getAvatarImage(): string - { - $media = $this->getFirstMedia('avatar'); - - if (! $media) { - return ''; - } - - return $media->getUrl(); - } - - /** - * Register media collections and conversions. - */ - public function registerMediaCollections(): void - { - $validateImage = function (File $file, ?array $allowedMimes = null) { - $allowedMimes ??= ['image/jpeg', 'image/png', 'image/webp', 'image/avif']; - - if (! in_array($file->mimeType, $allowedMimes)) { - throw new \Exception("Unsupported file type: {$file->mimeType}"); - } - - if ($file->size > 2 * 1024 * 1024) { - throw new \Exception('File too large. Max allowed size is 2MB.'); - } - - return true; - }; - - $this->addMediaCollection('avatar') - ->singleFile() - ->acceptsFile(fn ($file) => $validateImage($file)); - - $this->addMediaCollection('content') - ->singleFile() - ->acceptsFile(fn ($file) => $validateImage($file)); - } - - /** - * Get the activity log options for User model. - * Logs changes to the "name", "email", "username" and "password" attributes. - */ - public function getActivitylogOptions(): LogOptions - { - return LogOptions::defaults() - ->useLogName('user') - ->logOnly(['name', 'email', 'username', 'password']) - ->logOnlyDirty() - ->dontSubmitEmptyLogs(); - } - - /** - * Get the search fields for User model - * Searchs between "id", "name" and "username" attributes. - * - * @return array{id: int, name: string, username: string} - */ - public function toSearchableArray(): array - { - return [ - 'id' => (int) $this->id, - 'name' => $this->name, - 'username' => $this->username, - ]; - } - - /** - * Guard for access admin panel. - */ - public function canAccessPanel(\Filament\Panel $panel): bool - { - return $this->can('view-panel'); - } - - /** - * Create a new factory instance for the model. - */ - protected static function newFactory(): UserFactory - { - return UserFactory::new(); - } - - /** - * Get the attributes that should be cast. - * - * @return array - */ - protected function casts(): array - { - return [ - 'email_verified_at' => 'datetime', - 'password' => 'hashed', - ]; - } } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 98dd452..150e079 100755 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,10 +2,6 @@ namespace App\Providers; -use App\Models\User; -use App\Models\UserProfile; -use App\Observers\UserObserver; -use App\Observers\UserProfileObserver; use App\Services\SettingsService; use App\Settings\SeoSettings; use App\Settings\SiteSettings; @@ -13,9 +9,6 @@ use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Vite; use Illuminate\Support\ServiceProvider; -use Packages\React\Services\ReactService; -use Packages\Recommend\Services\FeedService; -use Packages\Search\Services\SearchService; class AppServiceProvider extends ServiceProvider { @@ -46,12 +39,7 @@ public function boot(): void }); } - $this->loadObservers(); $this->loadMacros(); - $this->configureSearch(); - $this->configureReact(); - $this->configureSearch(); - $this->configureFeed(); try { $siteSettings = SettingsService::getSiteSettings(); @@ -121,12 +109,6 @@ protected function loadSiteConfig(SiteSettings $siteSettings): void } } - protected function loadObservers(): void - { - User::observe(UserObserver::class); - UserProfile::observe(UserProfileObserver::class); - } - protected function loadMacros(): void { Builder::macro('existsOrFail', function ($message = '') { @@ -137,33 +119,4 @@ protected function loadMacros(): void return $this; }); } - - protected function configureSearch(): void - { - SearchService::registerHandler( - index: 'users', - callback: fn ($hit) => app(\App\Services\UserService::class)->getData($hit['id']), - searchableAttributes: ['name', 'username'], - filterableAttributes: ['id'], - sortableAttributes: ['created_at'] - ); - } - - protected function configureReact(): void - { - ReactService::registerHandler( - name: 'user', - class: User::class, - callback: fn ($slug) => app(\App\Services\UserService::class)->getId($slug) - ); - } - - protected function configureFeed(): void - { - FeedService::registerHandler( - name: 'user', - isFilter: true, - callback: fn ($slug) => app(\App\Services\UserService::class)->getId($slug) - ); - } } diff --git a/app/Services/SeoService.php b/app/Services/SeoService.php index 992a251..5db2bac 100755 --- a/app/Services/SeoService.php +++ b/app/Services/SeoService.php @@ -2,8 +2,6 @@ namespace App\Services; -use App\Data\UserData; -use App\Data\UserProfileData; use App\Settings\SeoSettings; use App\Settings\SiteSettings; use Carbon\Carbon; @@ -20,6 +18,8 @@ use Packages\Page\Data\PageData; use Packages\Tag\Data\TagData; use Packages\Tag\Data\TagProfileData; +use Packages\User\Data\UserData; +use Packages\User\Data\UserProfileData; use Spatie\SchemaOrg\Schema; class SeoService diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 7cb1430..7d38a54 100755 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -3,6 +3,7 @@ return [ App\Providers\AppServiceProvider::class, App\Providers\Filament\AdminPanelProvider::class, + Packages\User\UserProvider::class, Packages\Article\ArticleProvider::class, Packages\Category\CategoryProvider::class, Packages\News\NewsProvider::class, diff --git a/composer.json b/composer.json index 9d5ce57..5d4edb6 100755 --- a/composer.json +++ b/composer.json @@ -56,6 +56,9 @@ "App\\": "app/", "Database\\Factories\\": "database/factories/", "Database\\Seeders\\": "database/seeders/", + "Packages\\User\\": "packages/User/src/", + "Packages\\User\\Database\\Factories\\": "packages/User/database/factories/", + "Packages\\User\\Database\\Seeders\\": "packages/User/database/seeders/", "Packages\\Article\\": "packages/Article/src/", "Packages\\Article\\Database\\Factories\\": "packages/Article/database/factories/", "Packages\\Article\\Database\\Seeders\\": "packages/Article/database/seeders/", diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 0e2a738..93cd9c1 100755 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -3,6 +3,7 @@ namespace Database\Seeders; use Illuminate\Database\Seeder; +use Packages\User\Database\Seeders\UserSeeder; class DatabaseSeeder extends Seeder { diff --git a/packages/Article/src/Data/ArticleData.php b/packages/Article/src/Data/ArticleData.php index 57465fc..0c1581d 100755 --- a/packages/Article/src/Data/ArticleData.php +++ b/packages/Article/src/Data/ArticleData.php @@ -2,13 +2,13 @@ namespace Packages\Article\Data; -use App\Data\UserData; use DateTime; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; use Packages\Article\Models\Article; use Packages\Category\Data\CategoryData; use Packages\Tag\Data\TagData; +use Packages\User\Data\UserData; use Spatie\LaravelData\Data; class ArticleData extends Data diff --git a/packages/Article/src/Models/Article.php b/packages/Article/src/Models/Article.php index 0c2e95a..5a06c87 100755 --- a/packages/Article/src/Models/Article.php +++ b/packages/Article/src/Models/Article.php @@ -52,6 +52,7 @@ * @property-read int|null $tags_count * @property-read User $user * + * @method static \Packages\Article\Database\Factories\ArticleFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Article newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Article newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Article query() diff --git a/packages/Article/src/Services/ArticleService.php b/packages/Article/src/Services/ArticleService.php index 89c0f9d..c4e2f69 100755 --- a/packages/Article/src/Services/ArticleService.php +++ b/packages/Article/src/Services/ArticleService.php @@ -2,10 +2,10 @@ namespace Packages\Article\Services; -use App\Services\UserService; use Illuminate\Support\Facades\Cache; use Packages\Article\Data\ArticleData; use Packages\Article\Models\Article; +use Packages\User\Services\UserService; class ArticleService { diff --git a/packages/Article/tests/Feature/Http/Controllers/ArticleControllerTest.php b/packages/Article/tests/Feature/Http/Controllers/ArticleControllerTest.php index 179f284..658ce68 100644 --- a/packages/Article/tests/Feature/Http/Controllers/ArticleControllerTest.php +++ b/packages/Article/tests/Feature/Http/Controllers/ArticleControllerTest.php @@ -2,7 +2,6 @@ namespace Packages\Article\Tests\Feature\Http\Controllers; -use App\Data\UserData; use App\Services\SeoService; use Honeystone\Seo\Contracts\BuildsMetadata; use Inertia\Testing\AssertableInertia as Assert; @@ -12,6 +11,7 @@ use Packages\Article\Tests\TestCase; use Packages\Category\Data\CategoryData; use Packages\Tag\Data\TagData; +use Packages\User\Data\UserData; use Symfony\Component\HttpFoundation\Response; class ArticleControllerTest extends TestCase diff --git a/packages/Article/tests/Unit/Data/ArticleDataTest.php b/packages/Article/tests/Unit/Data/ArticleDataTest.php index 988ada2..6703f01 100644 --- a/packages/Article/tests/Unit/Data/ArticleDataTest.php +++ b/packages/Article/tests/Unit/Data/ArticleDataTest.php @@ -2,7 +2,6 @@ namespace Packages\Article\Tests\Unit\Data; -use App\Data\UserData; use App\Models\User; use Carbon\Carbon; use Mockery; @@ -12,6 +11,7 @@ use Packages\Article\Tests\TestCase; use Packages\Category\Data\CategoryData; use Packages\Tag\Data\TagData; +use Packages\User\Data\UserData; class ArticleDataTest extends TestCase { diff --git a/packages/Category/src/Models/Category.php b/packages/Category/src/Models/Category.php index 4a914ce..d593683 100755 --- a/packages/Category/src/Models/Category.php +++ b/packages/Category/src/Models/Category.php @@ -27,6 +27,7 @@ * @property-read int|null $followers_count * @property-read CategoryProfile|null $profile * + * @method static \Packages\Category\Database\Factories\CategoryFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Category newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Category newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Category query() diff --git a/packages/Category/src/Models/CategoryProfile.php b/packages/Category/src/Models/CategoryProfile.php index a777489..d7a1e3a 100755 --- a/packages/Category/src/Models/CategoryProfile.php +++ b/packages/Category/src/Models/CategoryProfile.php @@ -21,6 +21,7 @@ * @property-read int|null $activities_count * @property-read Category $category * + * @method static \Packages\Category\Database\Factories\CategoryProfileFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|CategoryProfile newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|CategoryProfile newQuery() * @method static \Illuminate\Database\Eloquent\Builder|CategoryProfile query() diff --git a/packages/Course/src/Data/CourseData.php b/packages/Course/src/Data/CourseData.php index 018c2ae..974abc9 100644 --- a/packages/Course/src/Data/CourseData.php +++ b/packages/Course/src/Data/CourseData.php @@ -2,13 +2,13 @@ namespace Packages\Course\Data; -use App\Data\UserData; use DateTime; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; use Packages\Category\Data\CategoryData; use Packages\Course\Models\Course; use Packages\Tag\Data\TagData; +use Packages\User\Data\UserData; use Spatie\LaravelData\Data; class CourseData extends Data diff --git a/packages/Course/src/Models/Course.php b/packages/Course/src/Models/Course.php index fb30f71..f3d5628 100644 --- a/packages/Course/src/Models/Course.php +++ b/packages/Course/src/Models/Course.php @@ -56,6 +56,7 @@ * @property-read int|null $tags_count * @property-read User $user * + * @method static \Packages\Course\Database\Factories\CourseFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Course newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Course newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Course query() diff --git a/packages/Course/src/Models/CourseChapter.php b/packages/Course/src/Models/CourseChapter.php index 31e29fe..9d414a1 100644 --- a/packages/Course/src/Models/CourseChapter.php +++ b/packages/Course/src/Models/CourseChapter.php @@ -23,6 +23,7 @@ * @property-read \Illuminate\Database\Eloquent\Collection $lessons * @property-read int|null $lessons_count * + * @method static \Packages\Course\Database\Factories\CourseChapterFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|CourseChapter newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|CourseChapter newQuery() * @method static \Illuminate\Database\Eloquent\Builder|CourseChapter query() diff --git a/packages/Course/src/Models/CourseLesson.php b/packages/Course/src/Models/CourseLesson.php index 94ddf72..ddb3e4b 100644 --- a/packages/Course/src/Models/CourseLesson.php +++ b/packages/Course/src/Models/CourseLesson.php @@ -40,6 +40,7 @@ * @property-read int|null $saves_count * @property-read User $user * + * @method static \Packages\Course\Database\Factories\CourseLessonFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|CourseLesson newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|CourseLesson newQuery() * @method static \Illuminate\Database\Eloquent\Builder|CourseLesson query() diff --git a/packages/Course/src/Services/CourseService.php b/packages/Course/src/Services/CourseService.php index 7f883bc..6e74661 100644 --- a/packages/Course/src/Services/CourseService.php +++ b/packages/Course/src/Services/CourseService.php @@ -2,13 +2,13 @@ namespace Packages\Course\Services; -use App\Services\UserService; use Illuminate\Support\Facades\Cache; use Packages\Course\Data\CourseData; use Packages\Course\Data\CourseLessonData; use Packages\Course\Models\Course; use Packages\Course\Models\CourseChapter; use Packages\Course\Models\CourseLesson; +use Packages\User\Services\UserService; class CourseService { diff --git a/packages/Entry/src/Data/EntryData.php b/packages/Entry/src/Data/EntryData.php index f0b4276..e12586e 100644 --- a/packages/Entry/src/Data/EntryData.php +++ b/packages/Entry/src/Data/EntryData.php @@ -2,11 +2,11 @@ namespace Packages\Entry\Data; -use App\Data\UserData; use DateTime; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; use Packages\Entry\Models\Entry; +use Packages\User\Data\UserData; use Spatie\LaravelData\Data; class EntryData extends Data diff --git a/packages/Entry/src/Models/Entry.php b/packages/Entry/src/Models/Entry.php index 92362f4..0c02a08 100644 --- a/packages/Entry/src/Models/Entry.php +++ b/packages/Entry/src/Models/Entry.php @@ -39,6 +39,7 @@ * @property-read int|null $saves_count * @property-read User $user * + * @method static \Packages\Entry\Database\Factories\EntryFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Entry newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Entry newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Entry query() diff --git a/packages/Entry/src/Services/EntryService.php b/packages/Entry/src/Services/EntryService.php index 0a4324c..018f6c9 100755 --- a/packages/Entry/src/Services/EntryService.php +++ b/packages/Entry/src/Services/EntryService.php @@ -2,10 +2,10 @@ namespace Packages\Entry\Services; -use App\Services\UserService; use Illuminate\Support\Facades\Cache; use Packages\Entry\Data\EntryData; use Packages\Entry\Models\Entry; +use Packages\User\Services\UserService; class EntryService { diff --git a/packages/News/src/Data/NewsData.php b/packages/News/src/Data/NewsData.php index 7704497..59d4c13 100755 --- a/packages/News/src/Data/NewsData.php +++ b/packages/News/src/Data/NewsData.php @@ -2,13 +2,13 @@ namespace Packages\News\Data; -use App\Data\UserData; -use App\Services\UserService; use DateTime; use Illuminate\Support\Facades\Gate; use Packages\Category\Data\CategoryData; use Packages\News\Models\News; use Packages\Tag\Data\TagData; +use Packages\User\Data\UserData; +use Packages\User\Services\UserService; use Spatie\LaravelData\Data; class NewsData extends Data diff --git a/packages/News/src/Models/News.php b/packages/News/src/Models/News.php index 2789594..461bb62 100755 --- a/packages/News/src/Models/News.php +++ b/packages/News/src/Models/News.php @@ -39,6 +39,7 @@ * @property-read int|null $tags_count * @property-read User $user * + * @method static \Packages\News\Database\Factories\NewsFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|News newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|News newQuery() * @method static \Illuminate\Database\Eloquent\Builder|News query() diff --git a/packages/News/src/Services/NewsService.php b/packages/News/src/Services/NewsService.php index 4c65ec0..e686499 100755 --- a/packages/News/src/Services/NewsService.php +++ b/packages/News/src/Services/NewsService.php @@ -2,10 +2,10 @@ namespace Packages\News\Services; -use App\Services\UserService; use Illuminate\Support\Facades\Cache; use Packages\News\Data\NewsData; use Packages\News\Models\News; +use Packages\User\Services\UserService; class NewsService { diff --git a/packages/Page/src/Data/PageData.php b/packages/Page/src/Data/PageData.php index 4bcbf26..a02edba 100755 --- a/packages/Page/src/Data/PageData.php +++ b/packages/Page/src/Data/PageData.php @@ -2,10 +2,10 @@ namespace Packages\Page\Data; -use App\Data\UserData; use DateTime; use Illuminate\Support\Facades\Gate; use Packages\Page\Models\Page; +use Packages\User\Data\UserData; use Spatie\LaravelData\Data; class PageData extends Data diff --git a/packages/Page/src/Models/Page.php b/packages/Page/src/Models/Page.php index 78dfc31..1cd169e 100755 --- a/packages/Page/src/Models/Page.php +++ b/packages/Page/src/Models/Page.php @@ -32,6 +32,7 @@ * @property-read int|null $media_count * @property-read User $user * + * @method static \Packages\Page\Database\Factories\PageFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Page newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Page newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Page query() diff --git a/packages/Page/src/Services/PageService.php b/packages/Page/src/Services/PageService.php index 87773fe..23de26e 100755 --- a/packages/Page/src/Services/PageService.php +++ b/packages/Page/src/Services/PageService.php @@ -2,10 +2,10 @@ namespace Packages\Page\Services; -use App\Services\UserService; use Illuminate\Support\Facades\Cache; use Packages\Page\Data\PageData; use Packages\Page\Models\Page; +use Packages\User\Services\UserService; class PageService { diff --git a/packages/React/src/Data/CommentData.php b/packages/React/src/Data/CommentData.php index 99fe5dd..a6215c8 100755 --- a/packages/React/src/Data/CommentData.php +++ b/packages/React/src/Data/CommentData.php @@ -2,11 +2,11 @@ namespace Packages\React\Data; -use App\Data\UserData; -use App\Services\UserService; use DateTime; use Illuminate\Support\Facades\Auth; use Packages\React\Models\Comment; +use Packages\User\Data\UserData; +use Packages\User\Services\UserService; use Spatie\LaravelData\Data; class CommentData extends Data diff --git a/packages/React/src/Models/Comment.php b/packages/React/src/Models/Comment.php index e3e525c..e1dcc3c 100644 --- a/packages/React/src/Models/Comment.php +++ b/packages/React/src/Models/Comment.php @@ -30,8 +30,11 @@ * @property-read \Illuminate\Database\Eloquent\Collection $likes * @property-read int|null $likes_count * @property-read Comment|null $parent + * @property-read \Illuminate\Database\Eloquent\Collection $replies + * @property-read int|null $replies_count * @property-read User $user * + * @method static \Packages\React\Database\Factories\CommentFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Comment newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Comment newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Comment query() diff --git a/packages/React/src/Models/Dislike.php b/packages/React/src/Models/Dislike.php index 7907296..f892ce6 100644 --- a/packages/React/src/Models/Dislike.php +++ b/packages/React/src/Models/Dislike.php @@ -17,6 +17,7 @@ * @property-read Model $dislikeable * @property-read User $user * + * @method static \Packages\React\Database\Factories\DislikeFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Dislike newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Dislike newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Dislike query() diff --git a/packages/React/src/Models/Follow.php b/packages/React/src/Models/Follow.php index baf9d62..0268808 100644 --- a/packages/React/src/Models/Follow.php +++ b/packages/React/src/Models/Follow.php @@ -17,6 +17,7 @@ * @property-read Model $followable * @property-read User $follower * + * @method static \Packages\React\Database\Factories\FollowFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Follow newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Follow newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Follow query() diff --git a/packages/React/src/Models/Like.php b/packages/React/src/Models/Like.php index 6fca666..b7bc13f 100644 --- a/packages/React/src/Models/Like.php +++ b/packages/React/src/Models/Like.php @@ -17,6 +17,7 @@ * @property-read Model $likeable * @property-read User $user * + * @method static \Packages\React\Database\Factories\LikeFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Like newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Like newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Like query() diff --git a/packages/React/src/Models/Save.php b/packages/React/src/Models/Save.php index 948b5e7..5157790 100644 --- a/packages/React/src/Models/Save.php +++ b/packages/React/src/Models/Save.php @@ -17,6 +17,7 @@ * @property-read Model $saveable * @property-read User $user * + * @method static \Packages\React\Database\Factories\SaveFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Save newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Save newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Save query() diff --git a/packages/Tag/src/Models/Tag.php b/packages/Tag/src/Models/Tag.php index 255d5ff..7e1cfff 100755 --- a/packages/Tag/src/Models/Tag.php +++ b/packages/Tag/src/Models/Tag.php @@ -27,6 +27,7 @@ * @property-read int|null $followers_count * @property-read TagProfile|null $profile * + * @method static \Packages\Tag\Database\Factories\TagFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Tag newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Tag newQuery() * @method static \Illuminate\Database\Eloquent\Builder|Tag query() diff --git a/packages/Tag/src/Models/TagProfile.php b/packages/Tag/src/Models/TagProfile.php index 6445c83..2d439bd 100755 --- a/packages/Tag/src/Models/TagProfile.php +++ b/packages/Tag/src/Models/TagProfile.php @@ -25,6 +25,7 @@ * @property-read int|null $categories_count * @property-read Tag $tag * + * @method static \Packages\Tag\Database\Factories\TagProfileFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|TagProfile newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|TagProfile newQuery() * @method static \Illuminate\Database\Eloquent\Builder|TagProfile query() diff --git a/database/factories/UserFactory.php b/packages/User/database/factories/UserFactory.php similarity index 95% rename from database/factories/UserFactory.php rename to packages/User/database/factories/UserFactory.php index ba8f72c..efae022 100755 --- a/database/factories/UserFactory.php +++ b/packages/User/database/factories/UserFactory.php @@ -1,6 +1,6 @@ + * @extends \Illuminate\Database\Eloquent\Factories\Factory<\Packages\User\Models\UserProfile> */ class UserProfileFactory extends Factory { diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/packages/User/database/migrations/0001_01_01_000000_create_users_table.php similarity index 100% rename from database/migrations/0001_01_01_000000_create_users_table.php rename to packages/User/database/migrations/0001_01_01_000000_create_users_table.php diff --git a/database/migrations/2025_08_20_154902_create_user_profiles_table.php b/packages/User/database/migrations/2025_08_20_154902_create_user_profiles_table.php similarity index 100% rename from database/migrations/2025_08_20_154902_create_user_profiles_table.php rename to packages/User/database/migrations/2025_08_20_154902_create_user_profiles_table.php diff --git a/database/seeders/UserSeeder.php b/packages/User/database/seeders/UserSeeder.php similarity index 91% rename from database/seeders/UserSeeder.php rename to packages/User/database/seeders/UserSeeder.php index c0098a2..75cf251 100755 --- a/database/seeders/UserSeeder.php +++ b/packages/User/database/seeders/UserSeeder.php @@ -1,11 +1,11 @@ prefix('api')->middleware(BatchLogsActivity::class)->group(function () { + Route::prefix('user')->name('user.')->group(function () { + Route::get('/{username}/preview', [UserController::class, 'preview'])->name('preview'); + }); + + Route::prefix('auth')->name('auth.')->group(function () { + Route::post('/login', [AuthController::class, 'login'])->name('login'); + Route::post('/register', [AuthController::class, 'register'])->name('register'); + Route::post('/forgot-password', [AuthController::class, 'forgotPassword'])->name('forgot-password'); + Route::post('/reset-password', [AuthController::class, 'resetPassword'])->name('reset-password.request'); + }); + + Route::middleware('auth')->group(function () { + Route::post('/auth/logout', [AuthController::class, 'logout'])->name('auth.logout'); + + Route::prefix('user/me/settings')->name('user.settings.')->group(function () { + Route::post('/account', [UserController::class, 'updateAccount'])->name('account')->can('update-account'); + Route::post('/profile', [UserController::class, 'updateProfile'])->name('profile')->can('update-profile'); + Route::post('/password', [UserController::class, 'updatePassword'])->name('password')->can('update-password'); + Route::post('/logout-other-sessions', [UserController::class, 'logOutOtherSessions'])->name('logout-other-sessions')->can('delete-sessions'); + Route::post('/delete-account', [UserController::class, 'deleteAccount'])->name('delete-account')->can('delete-account'); + }); + + Route::post('/user/me/verification/resend', [UserController::class, 'verificationResend'])->name('user.verification.resend'); + + Route::prefix('notifications')->name('notification.')->controller(NotificationController::class)->group(function () { + Route::get('/', 'list')->name('list')->can('view-notification'); + Route::get('/count', 'count')->name('count')->can('view-notification'); + Route::post('/mark-as-read', 'read')->name('read')->can('view-notification'); + Route::post('/mark-all-as-read', 'readAll')->name('read-all')->can('view-notification'); + }); + }); +}); diff --git a/packages/User/routes/web.php b/packages/User/routes/web.php new file mode 100755 index 0000000..1ec19e7 --- /dev/null +++ b/packages/User/routes/web.php @@ -0,0 +1,22 @@ +group(function () { + Route::get('/@{username}', [UserController::class, 'view'])->name('user.view'); + + Route::name('user.')->prefix('user')->controller(UserController::class)->middleware('auth')->group(function () { + Route::get('/me/settings', 'settingsView')->name('settings.view'); + Route::get('/me/verification', 'verificationView')->name('verification.view'); + Route::middleware('signed') + ->get('/me/verification/verify/{id}/{hash}', 'verificationVerify') + ->name('verification.verify'); + }); + + Route::get('/auth/reset-password', [AuthController::class, 'viewResetPassword'])->name('auth.reset-password.view'); +}); + +require __DIR__ . '/api.php'; diff --git a/app/Data/UserData.php b/packages/User/src/Data/UserData.php similarity index 94% rename from app/Data/UserData.php rename to packages/User/src/Data/UserData.php index 58f3350..207052c 100755 --- a/app/Data/UserData.php +++ b/packages/User/src/Data/UserData.php @@ -1,10 +1,10 @@ user(); - /** @var \App\Models\UserProfile $profileModel */ + /** @var \Packages\User\Models\UserProfile $profileModel */ $profileModel = $user->profile; $account = [ 'name' => $user->name, @@ -193,7 +194,7 @@ public function updateProfile(Request $request): HttpResponse ]); $user = $request->user(); - /** @var \App\Models\UserProfile $profile */ + /** @var \Packages\User\Models\UserProfile $profile */ $profile = $user->profile; if ($request->hasFile('cover')) { diff --git a/app/Http/Requests/Auth/ForgotPasswordRequest.php b/packages/User/src/Http/Requests/Auth/ForgotPasswordRequest.php similarity index 98% rename from app/Http/Requests/Auth/ForgotPasswordRequest.php rename to packages/User/src/Http/Requests/Auth/ForgotPasswordRequest.php index c1ccbb6..83173de 100755 --- a/app/Http/Requests/Auth/ForgotPasswordRequest.php +++ b/packages/User/src/Http/Requests/Auth/ForgotPasswordRequest.php @@ -1,6 +1,6 @@ user instanceof \App\Models\User ? $event->user : null; + /** @var User|null $user */ + $user = $event->user instanceof User ? $event->user : null; Activity::causedBy($user) ->performedOn($user) diff --git a/packages/User/src/Models/User.php b/packages/User/src/Models/User.php new file mode 100644 index 0000000..7677020 --- /dev/null +++ b/packages/User/src/Models/User.php @@ -0,0 +1,230 @@ + $activities + * @property-read int|null $activities_count + * @property-read mixed $breezy_session + * @property-read \Illuminate\Database\Eloquent\Collection $breezySessions + * @property-read int|null $breezy_sessions_count + * @property-read \Illuminate\Database\Eloquent\Collection $followers + * @property-read int|null $followers_count + * @property-read \Illuminate\Database\Eloquent\Collection $followings + * @property-read int|null $followings_count + * @property-read \Spatie\MediaLibrary\MediaCollections\Models\Collections\MediaCollection $media + * @property-read int|null $media_count + * @property-read \Illuminate\Notifications\DatabaseNotificationCollection $notifications + * @property-read int|null $notifications_count + * @property-read \Illuminate\Database\Eloquent\Collection $permissions + * @property-read int|null $permissions_count + * @property-read UserProfile|null $profile + * @property-read \Illuminate\Database\Eloquent\Collection $roles + * @property-read int|null $roles_count + * @property-read mixed $two_factor_recovery_codes + * @property-read mixed $two_factor_secret + * + * @method static \Packages\User\Database\Factories\UserFactory factory($count = null, $state = []) + * @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|User newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|User onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|User permission($permissions, $without = false) + * @method static \Illuminate\Database\Eloquent\Builder|User query() + * @method static \Illuminate\Database\Eloquent\Builder|User role($roles, $guard = null, $without = false) + * @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereEmail($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereEmailVerifiedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|User wherePassword($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereRememberToken($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereUsername($value) + * @method static \Illuminate\Database\Eloquent\Builder|User withTrashed(bool $withTrashed = true) + * @method static \Illuminate\Database\Eloquent\Builder|User withoutPermission($permissions) + * @method static \Illuminate\Database\Eloquent\Builder|User withoutRole($roles, $guard = null) + * @method static \Illuminate\Database\Eloquent\Builder|User withoutTrashed() + * + * @mixin \Eloquent + */ +class User extends Authenticatable implements FilamentUser, HasMedia, HasAvatar +{ + use CanFollow; + + /** @use HasFactory<\Packages\User\Database\Factories\UserFactory> */ + use HasFactory; + + use HasFollowers; + use HasRoles; + use InteractsWithMedia; + use LogsActivity; + use Notifiable; + use SoftDeletes; + use TwoFactorAuthenticatable; + use Searchable; + + /** + * The attributes that are mass assignable. + * + * @var list + */ + protected $fillable = [ + 'name', + 'email', + 'username', + 'password', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var list + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * @return HasOne + */ + public function profile(): HasOne + { + return $this->hasOne(UserProfile::class); + } + + public function getFilamentAvatarUrl(): ?string + { + return $this->getAvatarImage(); + } + + /** + * Get the avatar image of the user. + */ + public function getAvatarImage(): string + { + $media = $this->getFirstMedia('avatar'); + + if (! $media) { + return ''; + } + + return $media->getUrl(); + } + + /** + * Register media collections and conversions. + */ + public function registerMediaCollections(): void + { + $validateImage = function (File $file, ?array $allowedMimes = null) { + $allowedMimes ??= ['image/jpeg', 'image/png', 'image/webp', 'image/avif']; + + if (! in_array($file->mimeType, $allowedMimes)) { + throw new \Exception("Unsupported file type: {$file->mimeType}"); + } + + if ($file->size > 2 * 1024 * 1024) { + throw new \Exception('File too large. Max allowed size is 2MB.'); + } + + return true; + }; + + $this->addMediaCollection('avatar') + ->singleFile() + ->acceptsFile(fn ($file) => $validateImage($file)); + + $this->addMediaCollection('content') + ->singleFile() + ->acceptsFile(fn ($file) => $validateImage($file)); + } + + /** + * Get the activity log options for User model. + * Logs changes to the "name", "email", "username" and "password" attributes. + */ + public function getActivitylogOptions(): LogOptions + { + return LogOptions::defaults() + ->useLogName('user') + ->logOnly(['name', 'email', 'username', 'password']) + ->logOnlyDirty() + ->dontSubmitEmptyLogs(); + } + + /** + * Get the search fields for User model + * Searchs between "id", "name" and "username" attributes. + * + * @return array{id: int, name: string, username: string} + */ + public function toSearchableArray(): array + { + return [ + 'id' => (int) $this->id, + 'name' => $this->name, + 'username' => $this->username, + ]; + } + + /** + * Guard for access admin panel. + */ + public function canAccessPanel(\Filament\Panel $panel): bool + { + return $this->can('view-panel'); + } + + /** + * Create a new factory instance for the model. + */ + protected static function newFactory(): UserFactory + { + return UserFactory::new(); + } + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + ]; + } +} diff --git a/app/Models/UserProfile.php b/packages/User/src/Models/UserProfile.php similarity index 94% rename from app/Models/UserProfile.php rename to packages/User/src/Models/UserProfile.php index 53f0f4d..3cf19d6 100755 --- a/app/Models/UserProfile.php +++ b/packages/User/src/Models/UserProfile.php @@ -1,11 +1,11 @@ |UserProfile newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|UserProfile newQuery() * @method static \Illuminate\Database\Eloquent\Builder|UserProfile query() @@ -47,7 +47,7 @@ */ class UserProfile extends Model implements HasMedia { - /** @use HasFactory<\Database\Factories\UserProfileFactory> */ + /** @use HasFactory<\Packages\User\Database\Factories\UserProfileFactory> */ use HasFactory; use InteractsWithMedia; diff --git a/app/Observers/UserObserver.php b/packages/User/src/Observers/UserObserver.php similarity index 96% rename from app/Observers/UserObserver.php rename to packages/User/src/Observers/UserObserver.php index f0c72bc..90bc660 100755 --- a/app/Observers/UserObserver.php +++ b/packages/User/src/Observers/UserObserver.php @@ -1,12 +1,12 @@ logoutOtherDevices($password); + /** @var \Illuminate\Auth\SessionGuard $guard */ + $guard = Auth::guard('web'); + + $guard->logoutOtherDevices($password); $driver = config('session.driver'); diff --git a/app/Services/UserService.php b/packages/User/src/Services/UserService.php similarity index 90% rename from app/Services/UserService.php rename to packages/User/src/Services/UserService.php index 6f0048d..9328605 100755 --- a/app/Services/UserService.php +++ b/packages/User/src/Services/UserService.php @@ -1,12 +1,12 @@ loadPolicies(); + $this->loadObservers(); + $this->loadRoutes(); + $this->loadFactories(); + $this->loadSeeders(); + $this->loadMigrations(); + $this->configureReact(); + $this->configureFeed(); + } + + public function loadPolicies(): void + { + Gate::policy(User::class, UserPolicy::class); + } + + public function loadObservers(): void + { + User::observe(UserObserver::class); + UserProfile::observe(UserProfileObserver::class); + } + + public function loadRoutes(): void + { + Route::middleware('web') + ->namespace('Packages\User\Http\Controllers') + ->group(__DIR__ . '/../routes/web.php'); + } + + protected function loadFactories(): void + { + $this->loadFactoriesFrom(__DIR__ . '/../database/factories'); + } + + protected function loadSeeders(): void + { + if ($this->app->runningInConsole()) { + $this->publishes([ + __DIR__ . '/../database/seeders' => database_path('seeders/packages/user'), + ], 'seeders'); + } + } + + protected function loadMigrations(): void + { + $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); + if ($this->app->runningInConsole()) { + $this->publishes([ + __DIR__ . '/../database/migrations' => database_path('migrations/'), + ], 'migrations'); + } + } + + protected function configureSearch(): void + { + SearchService::registerHandler( + index: 'users', + callback: fn ($hit) => app(Services\UserService::class)->getData($hit['id']), + searchableAttributes: ['name', 'username'], + filterableAttributes: ['id'], + sortableAttributes: ['created_at'] + ); + } + + protected function configureReact(): void + { + ReactService::registerHandler( + name: 'user', + class: User::class, + callback: fn ($slug) => app(Services\UserService::class)->getId($slug) + ); + } + + protected function configureFeed(): void + { + FeedService::registerHandler( + name: 'user', + isFilter: true, + callback: fn ($slug) => app(Services\UserService::class)->getId($slug) + ); + } +} diff --git a/routes/api.php b/routes/api.php old mode 100755 new mode 100644 index a092982..b3d9bbc --- a/routes/api.php +++ b/routes/api.php @@ -1,41 +1 @@ prefix('api')->middleware(BatchLogsActivity::class)->group(function () { - Route::prefix('user')->name('user.')->group(function () { - Route::get('/{username}/preview', [UserController::class, 'preview'])->name('preview'); - }); - - Route::prefix('auth')->name('auth.')->group(function () { - Route::post('/login', [AuthController::class, 'login'])->name('login'); - Route::post('/register', [AuthController::class, 'register'])->name('register'); - Route::post('/forgot-password', [AuthController::class, 'forgotPassword'])->name('forgot-password'); - Route::post('/reset-password', [AuthController::class, 'resetPassword'])->name('reset-password.request'); - }); - - Route::middleware('auth')->group(function () { - Route::post('/auth/logout', [AuthController::class, 'logout'])->name('auth.logout'); - - Route::prefix('user/me/settings')->name('user.settings.')->group(function () { - Route::post('/account', [UserController::class, 'updateAccount'])->name('account')->can('update-account'); - Route::post('/profile', [UserController::class, 'updateProfile'])->name('profile')->can('update-profile'); - Route::post('/password', [UserController::class, 'updatePassword'])->name('password')->can('update-password'); - Route::post('/logout-other-sessions', [UserController::class, 'logOutOtherSessions'])->name('logout-other-sessions')->can('delete-sessions'); - Route::post('/delete-account', [UserController::class, 'deleteAccount'])->name('delete-account')->can('delete-account'); - }); - - Route::post('/user/me/verification/resend', [UserController::class, 'verificationResend'])->name('user.verification.resend'); - - Route::prefix('notifications')->name('notification.')->controller(NotificationController::class)->group(function () { - Route::get('/', 'list')->name('list')->can('view-notification'); - Route::get('/count', 'count')->name('count')->can('view-notification'); - Route::post('/mark-as-read', 'read')->name('read')->can('view-notification'); - Route::post('/mark-all-as-read', 'readAll')->name('read-all')->can('view-notification'); - }); - }); -}); diff --git a/routes/web.php b/routes/web.php old mode 100755 new mode 100644 index e6d5bcd..7cca023 --- a/routes/web.php +++ b/routes/web.php @@ -1,25 +1,8 @@ group(function () { Route::get('/', [HomeController::class, 'view'])->name('home.view'); - - Route::get('/@{username}', [UserController::class, 'view'])->name('user.view'); - - Route::name('user.')->prefix('user')->controller(UserController::class)->middleware('auth')->group(function () { - Route::get('/me/settings', 'settingsView')->name('settings.view'); - Route::get('/me/verification', 'verificationView')->name('verification.view'); - Route::middleware('signed') - ->get('/me/verification/verify/{id}/{hash}', 'verificationVerify') - ->name('verification.verify'); - }); - - Route::get('/auth/reset-password', [AuthController::class, 'viewResetPassword'])->name('auth.reset-password.view'); }); - -require __DIR__ . '/api.php';