feat: Implement user profile management and favorites module

- Added routes for user profile management including edit, update, and delete functionalities.
- Created ProfileController to handle profile-related requests.
- Introduced Profile model to manage user profile data.
- Developed user status states (Active, Banned, Suspended) with appropriate labels and descriptions.
- Implemented favorite listings and sellers functionality in the User model.
- Created views for profile editing, updating password, and deleting account.
- Added migration for user and profile tables along with necessary fields.
- Registered User module with service provider and routes.
This commit is contained in:
fatihalp 2026-03-05 01:23:42 +03:00
parent 72fbabb60b
commit 7e9d77c0a8
65 changed files with 817 additions and 936 deletions

View File

@ -1,21 +1,19 @@
<?php <?php
namespace Modules\Admin\Filament\Resources; namespace Modules\Admin\Filament\Resources;
use A909M\FilamentStateFusion\Forms\Components\StateFusionSelect;
use A909M\FilamentStateFusion\Tables\Columns\StateFusionSelectColumn; use A909M\FilamentStateFusion\Tables\Columns\StateFusionSelectColumn;
use A909M\FilamentStateFusion\Tables\Filters\StateFusionSelectFilter; use A909M\FilamentStateFusion\Tables\Filters\StateFusionSelectFilter;
use App\Models\User; use Modules\User\App\Models\User;
use BackedEnum; use BackedEnum;
use Filament\Actions\Action; use Filament\Actions\Action;
use Filament\Actions\DeleteAction; use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction; use Filament\Actions\EditAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource; use Filament\Resources\Resource;
use Filament\Schemas\Schema; use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn; use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table; use Filament\Tables\Table;
use Modules\Admin\Filament\Resources\UserResource\Pages; use Modules\Admin\Filament\Resources\UserResource\Pages;
use Modules\User\App\Support\Filament\UserFormFields;
use STS\FilamentImpersonate\Actions\Impersonate; use STS\FilamentImpersonate\Actions\Impersonate;
use UnitEnum; use UnitEnum;
@ -28,11 +26,11 @@ class UserResource extends Resource
public static function form(Schema $schema): Schema public static function form(Schema $schema): Schema
{ {
return $schema->schema([ return $schema->schema([
TextInput::make('name')->required()->maxLength(255), UserFormFields::name(),
TextInput::make('email')->email()->required()->maxLength(255)->unique(ignoreRecord: true), UserFormFields::email(),
TextInput::make('password')->password()->required(fn ($livewire) => $livewire instanceof Pages\CreateUser)->dehydrateStateUsing(fn ($state) => filled($state) ? bcrypt($state) : null)->dehydrated(fn ($state) => filled($state)), UserFormFields::password(fn ($livewire) => $livewire instanceof Pages\CreateUser),
StateFusionSelect::make('status')->required(), UserFormFields::status(),
Select::make('roles')->multiple()->relationship('roles', 'name')->preload(), UserFormFields::roles(),
]); ]);
} }

View File

@ -0,0 +1,120 @@
<?php
namespace Modules\Conversation\App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Modules\Conversation\App\Models\Conversation;
use Modules\Conversation\App\Support\QuickMessageCatalog;
use Modules\Listing\Models\Listing;
class ConversationController extends Controller
{
public function inbox(Request $request): View
{
$userId = (int) $request->user()->getKey();
$messageFilter = $this->resolveMessageFilter($request);
$conversations = Conversation::inboxForUser($userId, $messageFilter);
$selectedConversation = Conversation::resolveSelected($conversations, $request->integer('conversation'));
if ($selectedConversation) {
$selectedConversation->loadThread();
$selectedConversation->markAsReadFor($userId);
$conversations = $conversations->map(function (Conversation $conversation) use ($selectedConversation): Conversation {
if ((int) $conversation->getKey() === (int) $selectedConversation->getKey()) {
$conversation->unread_count = 0;
}
return $conversation;
});
}
return view('conversation::inbox', [
'conversations' => $conversations,
'selectedConversation' => $selectedConversation,
'messageFilter' => $messageFilter,
'quickMessages' => QuickMessageCatalog::all(),
]);
}
public function start(Request $request, Listing $listing): RedirectResponse
{
$user = $request->user();
if (! $listing->user_id) {
return back()->with('error', 'Bu ilan için mesajlaşma açılamadı.');
}
if ((int) $listing->user_id === (int) $user->getKey()) {
return back()->with('error', 'Kendi ilanına mesaj gönderemezsin.');
}
$conversation = Conversation::openForListingBuyer($listing, (int) $user->getKey());
$user->favoriteListings()->syncWithoutDetaching([$listing->getKey()]);
$messageBody = trim((string) $request->string('message'));
if ($messageBody !== '') {
$message = $conversation->messages()->create([
'sender_id' => $user->getKey(),
'body' => $messageBody,
]);
$conversation->forceFill(['last_message_at' => $message->created_at])->save();
}
return redirect()
->route('panel.inbox.index', array_merge($this->inboxFilters($request), ['conversation' => $conversation->getKey()]))
->with('success', $messageBody !== '' ? 'Mesaj gönderildi.' : 'Sohbet açıldı.');
}
public function send(Request $request, Conversation $conversation): RedirectResponse
{
$user = $request->user();
$userId = (int) $user->getKey();
if ((int) $conversation->buyer_id !== $userId && (int) $conversation->seller_id !== $userId) {
abort(403);
}
$payload = $request->validate([
'message' => ['required', 'string', 'max:2000'],
]);
$messageBody = trim($payload['message']);
if ($messageBody === '') {
return back()->with('error', 'Mesaj boş olamaz.');
}
$message = $conversation->messages()->create([
'sender_id' => $userId,
'body' => $messageBody,
]);
$conversation->forceFill(['last_message_at' => $message->created_at])->save();
return redirect()
->route('panel.inbox.index', array_merge($this->inboxFilters($request), ['conversation' => $conversation->getKey()]))
->with('success', 'Mesaj gönderildi.');
}
private function inboxFilters(Request $request): array
{
$messageFilter = $this->resolveMessageFilter($request);
return $messageFilter === 'all' ? [] : ['message_filter' => $messageFilter];
}
private function resolveMessageFilter(Request $request): string
{
$messageFilter = (string) $request->string('message_filter', 'all');
return in_array($messageFilter, ['all', 'unread', 'important'], true) ? $messageFilter : 'all';
}
}

View File

@ -0,0 +1,168 @@
<?php
namespace Modules\Conversation\App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Modules\Listing\Models\Listing;
use Modules\User\App\Models\User;
class Conversation extends Model
{
use HasFactory;
protected $fillable = ['listing_id', 'seller_id', 'buyer_id', 'last_message_at'];
protected $casts = ['last_message_at' => 'datetime'];
public function listing()
{
return $this->belongsTo(Listing::class);
}
public function seller()
{
return $this->belongsTo(User::class, 'seller_id');
}
public function buyer()
{
return $this->belongsTo(User::class, 'buyer_id');
}
public function messages()
{
return $this->hasMany(ConversationMessage::class);
}
public function lastMessage()
{
return $this->hasOne(ConversationMessage::class)
->latestOfMany()
->select([
'conversation_messages.id',
'conversation_messages.conversation_id',
'conversation_messages.sender_id',
'conversation_messages.body',
'conversation_messages.created_at',
]);
}
public function scopeForUser(Builder $query, int $userId): Builder
{
return $query->where(function (Builder $participantQuery) use ($userId): void {
$participantQuery->where('buyer_id', $userId)->orWhere('seller_id', $userId);
});
}
public function scopeApplyMessageFilter(Builder $query, int $userId, string $messageFilter): Builder
{
if (! in_array($messageFilter, ['unread', 'important'], true)) {
return $query;
}
return $query->whereHas('messages', function (Builder $messageQuery) use ($userId): void {
$messageQuery->where('sender_id', '!=', $userId)->whereNull('read_at');
});
}
public function scopeWithInboxData(Builder $query, int $userId): Builder
{
return $query
->with([
'listing:id,title,price,currency,user_id',
'buyer:id,name',
'seller:id,name',
'lastMessage',
'lastMessage.sender:id,name',
])
->withCount([
'messages as unread_count' => fn (Builder $messageQuery) => $messageQuery
->where('sender_id', '!=', $userId)
->whereNull('read_at'),
])
->orderByDesc('last_message_at')
->orderByDesc('updated_at');
}
public static function inboxForUser(int $userId, string $messageFilter = 'all'): EloquentCollection
{
return static::query()
->forUser($userId)
->applyMessageFilter($userId, $messageFilter)
->withInboxData($userId)
->get();
}
public static function resolveSelected(EloquentCollection $conversations, ?int $conversationId): ?self
{
$selectedConversationId = $conversationId;
if (($selectedConversationId ?? 0) <= 0 && $conversations->isNotEmpty()) {
$selectedConversationId = (int) $conversations->first()->getKey();
}
if (($selectedConversationId ?? 0) <= 0) {
return null;
}
$selected = $conversations->firstWhere('id', $selectedConversationId);
if (! $selected instanceof self) {
return null;
}
return $selected;
}
public function loadThread(): void
{
$this->load([
'listing:id,title,price,currency,user_id',
'messages' => fn (Builder $query) => $query->with('sender:id,name')->orderBy('created_at'),
]);
}
public function markAsReadFor(int $userId): void
{
ConversationMessage::query()
->where('conversation_id', $this->getKey())
->where('sender_id', '!=', $userId)
->whereNull('read_at')
->update([
'read_at' => now(),
'updated_at' => now(),
]);
}
public static function openForListingBuyer(Listing $listing, int $buyerId): self
{
$conversation = static::query()->firstOrCreate(
[
'listing_id' => $listing->getKey(),
'buyer_id' => $buyerId,
],
[
'seller_id' => $listing->user_id,
],
);
if ((int) $conversation->seller_id !== (int) $listing->user_id) {
$conversation->forceFill(['seller_id' => $listing->user_id])->save();
}
return $conversation;
}
public static function buyerListingConversationId(int $listingId, int $buyerId): ?int
{
$value = static::query()
->where('listing_id', $listingId)
->where('buyer_id', $buyerId)
->value('id');
return is_null($value) ? null : (int) $value;
}
}

View File

@ -1,24 +1,18 @@
<?php <?php
namespace App\Models; namespace Modules\Conversation\App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Modules\User\App\Models\User;
class ConversationMessage extends Model class ConversationMessage extends Model
{ {
use HasFactory; use HasFactory;
protected $fillable = [ protected $fillable = ['conversation_id', 'sender_id', 'body', 'read_at'];
'conversation_id',
'sender_id',
'body',
'read_at',
];
protected $casts = [ protected $casts = ['read_at' => 'datetime'];
'read_at' => 'datetime',
];
public function conversation() public function conversation()
{ {

View File

@ -0,0 +1,19 @@
<?php
namespace Modules\Conversation\App\Providers;
use Illuminate\Support\ServiceProvider;
class ConversationServiceProvider extends ServiceProvider
{
public function boot(): void
{
$this->loadMigrationsFrom(module_path('Conversation', 'database/migrations'));
$this->loadRoutesFrom(module_path('Conversation', 'routes/web.php'));
$this->loadViewsFrom(module_path('Conversation', 'resources/views'), 'conversation');
}
public function register(): void
{
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Modules\Conversation\App\Support;
class QuickMessageCatalog
{
public static function all(): array
{
return [
'Merhaba',
'İlan hâlâ satışta mı?',
'Son fiyat nedir?',
'Teşekkürler',
];
}
}

View File

@ -0,0 +1,46 @@
<?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
{
if (! Schema::hasTable('conversations')) {
Schema::create('conversations', function (Blueprint $table): void {
$table->id();
$table->foreignId('listing_id')->constrained('listings')->cascadeOnDelete();
$table->foreignId('seller_id')->constrained('users')->cascadeOnDelete();
$table->foreignId('buyer_id')->constrained('users')->cascadeOnDelete();
$table->timestamp('last_message_at')->nullable();
$table->timestamps();
$table->unique(['listing_id', 'buyer_id']);
$table->index(['seller_id', 'last_message_at']);
$table->index(['buyer_id', 'last_message_at']);
});
}
if (! Schema::hasTable('conversation_messages')) {
Schema::create('conversation_messages', function (Blueprint $table): void {
$table->id();
$table->foreignId('conversation_id')->constrained('conversations')->cascadeOnDelete();
$table->foreignId('sender_id')->constrained('users')->cascadeOnDelete();
$table->text('body');
$table->timestamp('read_at')->nullable();
$table->timestamps();
$table->index(['conversation_id', 'created_at']);
$table->index(['conversation_id', 'read_at']);
});
}
}
public function down(): void
{
Schema::dropIfExists('conversation_messages');
Schema::dropIfExists('conversations');
}
};

View File

@ -0,0 +1,11 @@
{
"name": "Conversation",
"alias": "conversation",
"description": "",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\Conversation\\App\\Providers\\ConversationServiceProvider"
],
"files": []
}

View File

@ -0,0 +1,13 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Conversation\App\Http\Controllers\ConversationController;
Route::middleware('auth')->prefix('panel')->name('panel.')->group(function () {
Route::get('/gelen-kutusu', [ConversationController::class, 'inbox'])->name('inbox.index');
});
Route::middleware('auth')->name('conversations.')->group(function () {
Route::post('/listings/{listing}/conversation', [ConversationController::class, 'start'])->name('start');
Route::post('/conversations/{conversation}/messages', [ConversationController::class, 'send'])->name('messages.send');
});

View File

@ -1,43 +1,42 @@
<?php <?php
namespace App\Http\Controllers; namespace Modules\Favorite\App\Http\Controllers;
use App\Models\Conversation; use App\Http\Controllers\Controller;
use App\Models\ConversationMessage;
use App\Models\FavoriteSearch;
use App\Models\User;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Modules\Category\Models\Category; use Modules\Category\Models\Category;
use Modules\Conversation\App\Models\Conversation;
use Modules\Conversation\App\Support\QuickMessageCatalog;
use Modules\Favorite\App\Models\FavoriteSearch;
use Modules\Listing\Models\Listing; use Modules\Listing\Models\Listing;
use Modules\User\App\Models\User;
class FavoriteController extends Controller class FavoriteController extends Controller
{ {
public function index(Request $request) public function index(Request $request)
{ {
$activeTab = (string) $request->string('tab', 'listings'); $activeTab = (string) $request->string('tab', 'listings');
if (! in_array($activeTab, ['listings', 'searches', 'sellers'], true)) { if (! in_array($activeTab, ['listings', 'searches', 'sellers'], true)) {
$activeTab = 'listings'; $activeTab = 'listings';
} }
$statusFilter = (string) $request->string('status', 'all'); $statusFilter = (string) $request->string('status', 'all');
if (! in_array($statusFilter, ['all', 'active'], true)) { if (! in_array($statusFilter, ['all', 'active'], true)) {
$statusFilter = 'all'; $statusFilter = 'all';
} }
$selectedCategoryId = $request->integer('category');
if ($selectedCategoryId <= 0) {
$selectedCategoryId = null;
}
$messageFilter = (string) $request->string('message_filter', 'all'); $messageFilter = (string) $request->string('message_filter', 'all');
if (! in_array($messageFilter, ['all', 'unread', 'important'], true)) { if (! in_array($messageFilter, ['all', 'unread', 'important'], true)) {
$messageFilter = 'all'; $messageFilter = 'all';
} }
$selectedCategoryId = $request->integer('category');
if ($selectedCategoryId <= 0) {
$selectedCategoryId = null;
}
$user = $request->user(); $user = $request->user();
$categories = Category::query() $categories = Category::query()
->where('is_active', true) ->where('is_active', true)
->orderBy('name') ->orderBy('name')
@ -49,12 +48,6 @@ class FavoriteController extends Controller
$conversations = collect(); $conversations = collect();
$selectedConversation = null; $selectedConversation = null;
$buyerConversationListingMap = []; $buyerConversationListingMap = [];
$quickMessages = [
'Merhaba',
'İlan hâlâ satışta mı?',
'Son fiyat nedir?',
'Teşekkürler',
];
if ($activeTab === 'listings') { if ($activeTab === 'listings') {
$favoriteListings = $user->favoriteListings() $favoriteListings = $user->favoriteListings()
@ -67,58 +60,18 @@ class FavoriteController extends Controller
->withQueryString(); ->withQueryString();
$userId = (int) $user->getKey(); $userId = (int) $user->getKey();
$conversations = Conversation::query() $conversations = Conversation::inboxForUser($userId, $messageFilter);
->forUser($userId)
->when(in_array($messageFilter, ['unread', 'important'], true), fn ($query) => $query->whereHas('messages', fn ($messageQuery) => $messageQuery
->where('sender_id', '!=', $userId)
->whereNull('read_at')))
->with([
'listing:id,title,price,currency,user_id',
'buyer:id,name',
'seller:id,name',
'lastMessage',
'lastMessage.sender:id,name',
])
->withCount([
'messages as unread_count' => fn ($query) => $query
->where('sender_id', '!=', $userId)
->whereNull('read_at'),
])
->orderByDesc('last_message_at')
->orderByDesc('updated_at')
->get();
$buyerConversationListingMap = $conversations $buyerConversationListingMap = $conversations
->where('buyer_id', $userId) ->where('buyer_id', $userId)
->pluck('id', 'listing_id') ->pluck('id', 'listing_id')
->map(fn ($conversationId) => (int) $conversationId) ->map(fn ($conversationId) => (int) $conversationId)
->all(); ->all();
$selectedConversationId = $request->integer('conversation'); $selectedConversation = Conversation::resolveSelected($conversations, $request->integer('conversation'));
if ($selectedConversationId <= 0 && $conversations->isNotEmpty()) {
$selectedConversationId = (int) $conversations->first()->getKey();
}
if ($selectedConversationId > 0) {
$selectedConversation = $conversations->firstWhere('id', $selectedConversationId);
if ($selectedConversation) { if ($selectedConversation) {
$selectedConversation->load([ $selectedConversation->loadThread();
'listing:id,title,price,currency,user_id', $selectedConversation->markAsReadFor($userId);
'messages' => fn ($query) => $query
->with('sender:id,name')
->orderBy('created_at'),
]);
ConversationMessage::query()
->where('conversation_id', $selectedConversation->getKey())
->where('sender_id', '!=', $userId)
->whereNull('read_at')
->update([
'read_at' => now(),
'updated_at' => now(),
]);
$conversations = $conversations->map(function (Conversation $conversation) use ($selectedConversation): Conversation { $conversations = $conversations->map(function (Conversation $conversation) use ($selectedConversation): Conversation {
if ((int) $conversation->getKey() === (int) $selectedConversation->getKey()) { if ((int) $conversation->getKey() === (int) $selectedConversation->getKey()) {
@ -129,7 +82,6 @@ class FavoriteController extends Controller
}); });
} }
} }
}
if ($activeTab === 'searches') { if ($activeTab === 'searches') {
$favoriteSearches = $user->favoriteSearches() $favoriteSearches = $user->favoriteSearches()
@ -149,7 +101,7 @@ class FavoriteController extends Controller
->withQueryString(); ->withQueryString();
} }
return view('favorites.index', [ return view('favorite::index', [
'activeTab' => $activeTab, 'activeTab' => $activeTab,
'statusFilter' => $statusFilter, 'statusFilter' => $statusFilter,
'selectedCategoryId' => $selectedCategoryId, 'selectedCategoryId' => $selectedCategoryId,
@ -161,24 +113,15 @@ class FavoriteController extends Controller
'conversations' => $conversations, 'conversations' => $conversations,
'selectedConversation' => $selectedConversation, 'selectedConversation' => $selectedConversation,
'buyerConversationListingMap' => $buyerConversationListingMap, 'buyerConversationListingMap' => $buyerConversationListingMap,
'quickMessages' => $quickMessages, 'quickMessages' => QuickMessageCatalog::all(),
]); ]);
} }
public function toggleListing(Request $request, Listing $listing) public function toggleListing(Request $request, Listing $listing)
{ {
$user = $request->user(); $isNowFavorite = $request->user()->toggleFavoriteListing($listing);
$isFavorite = $user->favoriteListings()->whereKey($listing->getKey())->exists();
if ($isFavorite) { return back()->with('success', $isNowFavorite ? 'İlan favorilere eklendi.' : 'İlan favorilerden kaldırıldı.');
$user->favoriteListings()->detach($listing->getKey());
return back()->with('success', 'İlan favorilerden kaldırıldı.');
}
$user->favoriteListings()->syncWithoutDetaching([$listing->getKey()]);
return back()->with('success', 'İlan favorilere eklendi.');
} }
public function toggleSeller(Request $request, User $seller) public function toggleSeller(Request $request, User $seller)
@ -189,17 +132,9 @@ class FavoriteController extends Controller
return back()->with('error', 'Kendi hesabını favorilere ekleyemezsin.'); return back()->with('error', 'Kendi hesabını favorilere ekleyemezsin.');
} }
$isFavorite = $user->favoriteSellers()->whereKey($seller->getKey())->exists(); $isNowFavorite = $user->toggleFavoriteSeller($seller);
if ($isFavorite) { return back()->with('success', $isNowFavorite ? 'Satıcı favorilere eklendi.' : 'Satıcı favorilerden kaldırıldı.');
$user->favoriteSellers()->detach($seller->getKey());
return back()->with('success', 'Satıcı favorilerden kaldırıldı.');
}
$user->favoriteSellers()->syncWithoutDetaching([$seller->getKey()]);
return back()->with('success', 'Satıcı favorilere eklendi.');
} }
public function storeSearch(Request $request) public function storeSearch(Request $request)
@ -225,15 +160,7 @@ class FavoriteController extends Controller
$categoryName = Category::query()->whereKey($filters['category'])->value('name'); $categoryName = Category::query()->whereKey($filters['category'])->value('name');
} }
$labelParts = []; $label = FavoriteSearch::labelFor($filters, is_string($categoryName) ? $categoryName : null);
if (! empty($filters['search'])) {
$labelParts[] = '"'.$filters['search'].'"';
}
if ($categoryName) {
$labelParts[] = $categoryName;
}
$label = $labelParts !== [] ? implode(' · ', $labelParts) : 'Filtreli arama';
$favoriteSearch = $request->user()->favoriteSearches()->firstOrCreate( $favoriteSearch = $request->user()->favoriteSearches()->firstOrCreate(
['signature' => $signature], ['signature' => $signature],

View File

@ -1,23 +1,16 @@
<?php <?php
namespace App\Models; namespace Modules\Favorite\App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Modules\Category\Models\Category;
use Modules\User\App\Models\User;
class FavoriteSearch extends Model class FavoriteSearch extends Model
{ {
protected $fillable = [ protected $fillable = ['user_id', 'label', 'search_term', 'category_id', 'filters', 'signature'];
'user_id',
'label',
'search_term',
'category_id',
'filters',
'signature',
];
protected $casts = [ protected $casts = ['filters' => 'array'];
'filters' => 'array',
];
public function user() public function user()
{ {
@ -26,7 +19,7 @@ class FavoriteSearch extends Model
public function category() public function category()
{ {
return $this->belongsTo(\Modules\Category\Models\Category::class); return $this->belongsTo(Category::class);
} }
public static function normalizeFilters(array $filters): array public static function normalizeFilters(array $filters): array
@ -45,4 +38,19 @@ class FavoriteSearch extends Model
return hash('sha256', is_string($payload) ? $payload : ''); return hash('sha256', is_string($payload) ? $payload : '');
} }
public static function labelFor(array $filters, ?string $categoryName = null): string
{
$labelParts = [];
if (! empty($filters['search'])) {
$labelParts[] = '"'.$filters['search'].'"';
}
if (filled($categoryName)) {
$labelParts[] = $categoryName;
}
return $labelParts !== [] ? implode(' · ', $labelParts) : 'Filtreli arama';
}
} }

View File

@ -0,0 +1,19 @@
<?php
namespace Modules\Favorite\App\Providers;
use Illuminate\Support\ServiceProvider;
class FavoriteServiceProvider extends ServiceProvider
{
public function boot(): void
{
$this->loadMigrationsFrom(module_path('Favorite', 'database/migrations'));
$this->loadRoutesFrom(module_path('Favorite', 'routes/web.php'));
$this->loadViewsFrom(module_path('Favorite', 'resources/views'), 'favorite');
}
public function register(): void
{
}
}

View File

@ -0,0 +1,55 @@
<?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
{
if (! Schema::hasTable('favorite_listings')) {
Schema::create('favorite_listings', function (Blueprint $table): void {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('listing_id')->constrained('listings')->cascadeOnDelete();
$table->timestamps();
$table->unique(['user_id', 'listing_id']);
});
}
if (! Schema::hasTable('favorite_sellers')) {
Schema::create('favorite_sellers', function (Blueprint $table): void {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('seller_id')->constrained('users')->cascadeOnDelete();
$table->timestamps();
$table->unique(['user_id', 'seller_id']);
});
}
if (! Schema::hasTable('favorite_searches')) {
Schema::create('favorite_searches', function (Blueprint $table): void {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('label')->nullable();
$table->string('search_term')->nullable();
$table->foreignId('category_id')->nullable()->constrained('categories')->nullOnDelete();
$table->json('filters')->nullable();
$table->string('signature', 64);
$table->timestamps();
$table->unique(['user_id', 'signature']);
});
}
}
public function down(): void
{
Schema::dropIfExists('favorite_searches');
Schema::dropIfExists('favorite_sellers');
Schema::dropIfExists('favorite_listings');
}
};

View File

@ -0,0 +1,11 @@
{
"name": "Favorite",
"alias": "favorite",
"description": "",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\Favorite\\App\\Providers\\FavoriteServiceProvider"
],
"files": []
}

View File

@ -0,0 +1,12 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Favorite\App\Http\Controllers\FavoriteController;
Route::middleware('auth')->prefix('favorites')->name('favorites.')->group(function () {
Route::get('/', [FavoriteController::class, 'index'])->name('index');
Route::post('/listings/{listing}/toggle', [FavoriteController::class, 'toggleListing'])->name('listings.toggle');
Route::post('/sellers/{seller}/toggle', [FavoriteController::class, 'toggleSeller'])->name('sellers.toggle');
Route::post('/searches', [FavoriteController::class, 'storeSearch'])->name('searches.store');
Route::delete('/searches/{favoriteSearch}', [FavoriteController::class, 'destroySearch'])->name('searches.destroy');
});

View File

@ -2,7 +2,7 @@
namespace Modules\Listing\Database\Seeders; namespace Modules\Listing\Database\Seeders;
use App\Models\User; use Modules\User\App\Models\User;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -11,9 +11,6 @@ use Modules\Listing\Models\Listing;
class ListingSeeder extends Seeder class ListingSeeder extends Seeder
{ {
/**
* @var array<int, array{title: string, description: string, price: int, city: string, country: string, image: string}>
*/
private const LISTINGS = [ private const LISTINGS = [
[ [
'title' => 'iPhone 14 Pro 256 GB, temiz kullanılmış', 'title' => 'iPhone 14 Pro 256 GB, temiz kullanılmış',
@ -106,9 +103,6 @@ class ListingSeeder extends Seeder
->first(); ->first();
} }
/**
* @param array{title: string, description: string, price: int, city: string, country: string, image: string} $data
*/
private function upsertListing(int $index, array $data, Collection $categories, User $user): Listing private function upsertListing(int $index, array $data, Collection $categories, User $user): Listing
{ {
$slug = Str::slug($data['title']) . '-' . ($index + 1); $slug = Str::slug($data['title']) . '-' . ($index + 1);
@ -167,7 +161,11 @@ class ListingSeeder extends Seeder
$media = $mediaItems->first(); $media = $mediaItems->first();
if (! $media || (string) $media->file_name !== $targetFileName) { if (
! $media
|| (string) $media->file_name !== $targetFileName
|| (string) $media->disk !== 'public'
) {
return false; return false;
} }

View File

@ -2,8 +2,8 @@
namespace Modules\Listing\Http\Controllers; namespace Modules\Listing\Http\Controllers;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Conversation; use Modules\Conversation\App\Models\Conversation;
use App\Models\FavoriteSearch; use Modules\Favorite\App\Models\FavoriteSearch;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;

View File

@ -55,18 +55,18 @@ class Listing extends Model implements HasMedia
public function user() public function user()
{ {
return $this->belongsTo(\App\Models\User::class); return $this->belongsTo(\Modules\User\App\Models\User::class);
} }
public function favoritedByUsers() public function favoritedByUsers()
{ {
return $this->belongsToMany(\App\Models\User::class, 'favorite_listings') return $this->belongsToMany(\Modules\User\App\Models\User::class, 'favorite_listings')
->withTimestamps(); ->withTimestamps();
} }
public function conversations() public function conversations()
{ {
return $this->hasMany(\App\Models\Conversation::class); return $this->hasMany(\Modules\Conversation\App\Models\Conversation::class);
} }
public function scopePublicFeed(Builder $query): Builder public function scopePublicFeed(Builder $query): Builder

View File

@ -20,7 +20,7 @@ use Modules\Listing\Support\ListingPanelHelper;
use Modules\Location\Models\City; use Modules\Location\Models\City;
use Modules\Location\Models\Country; use Modules\Location\Models\Country;
use Modules\Partner\Filament\Resources\ListingResource; use Modules\Partner\Filament\Resources\ListingResource;
use Modules\Profile\Models\Profile; use Modules\User\App\Models\Profile;
use Throwable; use Throwable;
class QuickCreateListing extends Page class QuickCreateListing extends Page

View File

@ -2,7 +2,7 @@
namespace Modules\Partner\Providers; namespace Modules\Partner\Providers;
use A909M\FilamentStateFusion\FilamentStateFusionPlugin; use A909M\FilamentStateFusion\FilamentStateFusionPlugin;
use App\Models\User; use Modules\User\App\Models\User;
use DutchCodingCompany\FilamentDeveloperLogins\FilamentDeveloperLoginsPlugin; use DutchCodingCompany\FilamentDeveloperLogins\FilamentDeveloperLoginsPlugin;
use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin; use DutchCodingCompany\FilamentSocialite\FilamentSocialitePlugin;
use Filament\Http\Middleware\Authenticate; use Filament\Http\Middleware\Authenticate;

View File

@ -1,34 +0,0 @@
<?php
namespace Modules\Profile\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Modules\Profile\Models\Profile;
class ProfileController extends Controller
{
public function show()
{
$profile = Profile::firstOrCreate(['user_id' => auth()->id()]);
return view('profile::show', compact('profile'));
}
public function edit()
{
$profile = Profile::firstOrCreate(['user_id' => auth()->id()]);
return view('profile::edit', compact('profile'));
}
public function update(Request $request)
{
$data = $request->validate([
'bio' => 'nullable|string|max:500',
'phone' => 'nullable|string|max:20',
'city' => 'nullable|string|max:100',
'country' => 'nullable|string|max:100',
'website' => 'nullable|url',
]);
Profile::updateOrCreate(['user_id' => auth()->id()], $data);
return redirect()->route('profile.show')->with('success', 'Profile updated!');
}
}

View File

@ -1,18 +0,0 @@
<?php
namespace Modules\Profile\Providers;
use Illuminate\Support\ServiceProvider;
class ProfileServiceProvider extends ServiceProvider
{
protected string $moduleName = 'Profile';
public function boot(): void
{
$this->loadMigrationsFrom(module_path($this->moduleName, 'database/migrations'));
$this->loadRoutesFrom(module_path($this->moduleName, 'routes/web.php'));
$this->loadViewsFrom(module_path($this->moduleName, 'resources/views'), 'profile');
}
public function register(): void {}
}

View File

@ -1,28 +0,0 @@
<?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('profiles', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->unique()->constrained('users')->cascadeOnDelete();
$table->string('avatar')->nullable();
$table->text('bio')->nullable();
$table->string('phone')->nullable();
$table->string('city')->nullable();
$table->string('country')->nullable();
$table->string('website')->nullable();
$table->boolean('is_verified')->default(false);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('profiles');
}
};

View File

@ -1,12 +0,0 @@
{
"name": "Profile",
"alias": "profile",
"description": "User profile management",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\Profile\\Providers\\ProfileServiceProvider"
],
"aliases": {},
"files": []
}

View File

@ -1,34 +0,0 @@
@extends('app::layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<div class="max-w-2xl mx-auto">
<h1 class="text-2xl font-bold mb-6">Edit Profile</h1>
<form method="POST" action="{{ route('profile.update') }}" class="bg-white rounded-lg shadow-md p-6 space-y-4">
@csrf @method('PUT')
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Bio</label>
<textarea name="bio" rows="3" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">{{ old('bio', $profile->bio) }}</textarea>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Phone</label>
<input type="text" name="phone" value="{{ old('phone', $profile->phone) }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">City</label>
<input type="text" name="city" value="{{ old('city', $profile->city) }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Country</label>
<input type="text" name="country" value="{{ old('country', $profile->country) }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Website</label>
<input type="url" name="website" value="{{ old('website', $profile->website) }}" class="w-full border rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500">
</div>
<button type="submit" class="w-full bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 transition font-medium">Save Profile</button>
</form>
</div>
</div>
@endsection

View File

@ -1,25 +0,0 @@
@extends('app::layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<div class="max-w-2xl mx-auto bg-white rounded-lg shadow-md p-6">
<div class="flex items-center space-x-4 mb-6">
<div class="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center">
<span class="text-2xl font-bold text-blue-600">{{ substr(auth()->user()->name, 0, 1) }}</span>
</div>
<div>
<h1 class="text-2xl font-bold">{{ auth()->user()->name }}</h1>
<p class="text-gray-500">{{ auth()->user()->email }}</p>
</div>
</div>
@if($profile->bio)<p class="text-gray-700 mb-4">{{ $profile->bio }}</p>@endif
<div class="space-y-2 text-gray-600">
@if($profile->phone)<p>📞 {{ $profile->phone }}</p>@endif
@if($profile->city)<p>📍 {{ $profile->city }}@if($profile->country), {{ $profile->country }}@endif</p>@endif
@if($profile->website)<p>🌐 <a href="{{ $profile->website }}" class="text-blue-600 hover:underline">{{ $profile->website }}</a></p>@endif
</div>
<div class="mt-6">
<a href="{{ route('profile.edit') }}" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition">Edit Profile</a>
</div>
</div>
</div>
@endsection

View File

@ -1,9 +0,0 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\Profile\Http\Controllers\ProfileController;
Route::middleware('auth')->prefix('profile')->name('profile.')->group(function () {
Route::get('/', [ProfileController::class, 'show'])->name('show');
Route::get('/edit', [ProfileController::class, 'edit'])->name('edit');
Route::put('/extended', [ProfileController::class, 'update'])->name('update');
});

View File

@ -1,23 +1,33 @@
<?php <?php
namespace App\Http\Controllers\Auth; namespace Modules\User\App\Http\Controllers;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule; use Illuminate\Validation\Rule;
use Illuminate\View\View;
class ProfileController extends Controller class ProfileController extends Controller
{ {
/** public function edit(Request $request): View
* Update the user's profile information. {
*/ return view('user::profile.edit', ['user' => $request->user()]);
}
public function update(Request $request): RedirectResponse public function update(Request $request): RedirectResponse
{ {
$validated = $request->validateWithBag('updateProfile', [ $validated = $request->validateWithBag('updateProfile', [
'name' => ['required', 'string', 'max:255'], 'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique('users')->ignore($request->user()->id)], 'email' => [
'required',
'string',
'lowercase',
'email',
'max:255',
Rule::unique('users')->ignore($request->user()->id),
],
]); ]);
$request->user()->fill($validated); $request->user()->fill($validated);
@ -28,12 +38,9 @@ class ProfileController extends Controller
$request->user()->save(); $request->user()->save();
return redirect('/profile')->with('status', 'profile-updated'); return redirect()->route('profile.edit')->with('status', 'profile-updated');
} }
/**
* Delete the user's account.
*/
public function destroy(Request $request): RedirectResponse public function destroy(Request $request): RedirectResponse
{ {
$request->validateWithBag('userDeletion', [ $request->validateWithBag('userDeletion', [
@ -43,7 +50,6 @@ class ProfileController extends Controller
$user = $request->user(); $user = $request->user();
Auth::logout(); Auth::logout();
$user->delete(); $user->delete();
$request->session()->invalidate(); $request->session()->invalidate();

View File

@ -1,5 +1,6 @@
<?php <?php
namespace Modules\Profile\Models;
namespace Modules\User\App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\LogOptions;
@ -10,6 +11,7 @@ class Profile extends Model
use LogsActivity; use LogsActivity;
protected $fillable = ['user_id', 'avatar', 'bio', 'phone', 'city', 'country', 'website', 'is_verified']; protected $fillable = ['user_id', 'avatar', 'bio', 'phone', 'city', 'country', 'website', 'is_verified'];
protected $casts = ['is_verified' => 'boolean']; protected $casts = ['is_verified' => 'boolean'];
public function getActivitylogOptions(): LogOptions public function getActivitylogOptions(): LogOptions
@ -22,6 +24,6 @@ class Profile extends Model
public function user() public function user()
{ {
return $this->belongsTo(\App\Models\User::class); return $this->belongsTo(User::class);
} }
} }

View File

@ -1,11 +1,12 @@
<?php <?php
namespace App\Models;
use App\States\UserStatus; namespace Modules\User\App\Models;
use Filament\Models\Contracts\HasAvatar;
use Filament\Models\Contracts\FilamentUser; use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasAvatar;
use Filament\Models\Contracts\HasTenants; use Filament\Models\Contracts\HasTenants;
use Filament\Panel; use Filament\Panel;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
@ -14,6 +15,11 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Jeffgreco13\FilamentBreezy\Traits\TwoFactorAuthenticatable; use Jeffgreco13\FilamentBreezy\Traits\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens; use Laravel\Sanctum\HasApiTokens;
use Modules\Conversation\App\Models\Conversation;
use Modules\Conversation\App\Models\ConversationMessage;
use Modules\Favorite\App\Models\FavoriteSearch;
use Modules\Listing\Models\Listing;
use Modules\User\App\States\UserStatus;
use Spatie\Activitylog\LogOptions; use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity; use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\ModelStates\HasStates; use Spatie\ModelStates\HasStates;
@ -21,9 +27,16 @@ use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable implements FilamentUser, HasTenants, HasAvatar class User extends Authenticatable implements FilamentUser, HasTenants, HasAvatar
{ {
use HasApiTokens, HasFactory, HasRoles, LogsActivity, Notifiable, HasStates, TwoFactorAuthenticatable; use HasApiTokens;
use HasFactory;
use HasRoles;
use LogsActivity;
use Notifiable;
use HasStates;
use TwoFactorAuthenticatable;
protected $fillable = ['name', 'email', 'password', 'avatar_url', 'status']; protected $fillable = ['name', 'email', 'password', 'avatar_url', 'status'];
protected $hidden = ['password', 'remember_token']; protected $hidden = ['password', 'remember_token'];
protected function casts(): array protected function casts(): array
@ -35,6 +48,11 @@ class User extends Authenticatable implements FilamentUser, HasTenants, HasAvata
]; ];
} }
protected static function newFactory(): Factory
{
return \Database\Factories\UserFactory::new();
}
public function getActivitylogOptions(): LogOptions public function getActivitylogOptions(): LogOptions
{ {
return LogOptions::defaults() return LogOptions::defaults()
@ -64,19 +82,22 @@ class User extends Authenticatable implements FilamentUser, HasTenants, HasAvata
public function listings() public function listings()
{ {
return $this->hasMany(\Modules\Listing\Models\Listing::class); return $this->hasMany(Listing::class);
}
public function profile()
{
return $this->hasOne(Profile::class);
} }
public function favoriteListings() public function favoriteListings()
{ {
return $this->belongsToMany(\Modules\Listing\Models\Listing::class, 'favorite_listings') return $this->belongsToMany(Listing::class, 'favorite_listings')->withTimestamps();
->withTimestamps();
} }
public function favoriteSellers() public function favoriteSellers()
{ {
return $this->belongsToMany(self::class, 'favorite_sellers', 'user_id', 'seller_id') return $this->belongsToMany(self::class, 'favorite_sellers', 'user_id', 'seller_id')->withTimestamps();
->withTimestamps();
} }
public function favoriteSearches() public function favoriteSearches()
@ -111,8 +132,40 @@ class User extends Authenticatable implements FilamentUser, HasTenants, HasAvata
public function getFilamentAvatarUrl(): ?string public function getFilamentAvatarUrl(): ?string
{ {
return filled($this->avatar_url) return filled($this->avatar_url) ? Storage::disk('public')->url($this->avatar_url) : null;
? Storage::disk('public')->url($this->avatar_url) }
: null;
public function toggleFavoriteListing(Listing $listing): bool
{
$isFavorite = $this->favoriteListings()->whereKey($listing->getKey())->exists();
if ($isFavorite) {
$this->favoriteListings()->detach($listing->getKey());
return false;
}
$this->favoriteListings()->syncWithoutDetaching([$listing->getKey()]);
return true;
}
public function toggleFavoriteSeller(self $seller): bool
{
if ((int) $seller->getKey() === (int) $this->getKey()) {
return false;
}
$isFavorite = $this->favoriteSellers()->whereKey($seller->getKey())->exists();
if ($isFavorite) {
$this->favoriteSellers()->detach($seller->getKey());
return false;
}
$this->favoriteSellers()->syncWithoutDetaching([$seller->getKey()]);
return true;
} }
} }

View File

@ -0,0 +1,19 @@
<?php
namespace Modules\User\App\Providers;
use Illuminate\Support\ServiceProvider;
class UserServiceProvider extends ServiceProvider
{
public function boot(): void
{
$this->loadMigrationsFrom(module_path('User', 'database/migrations'));
$this->loadRoutesFrom(module_path('User', 'routes/web.php'));
$this->loadViewsFrom(module_path('User', 'resources/views'), 'user');
}
public function register(): void
{
}
}

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\States; namespace Modules\User\App\States;
use Filament\Support\Contracts\HasColor; use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasDescription; use Filament\Support\Contracts\HasDescription;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\States; namespace Modules\User\App\States;
use Filament\Support\Contracts\HasColor; use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasDescription; use Filament\Support\Contracts\HasDescription;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\States; namespace Modules\User\App\States;
use Filament\Support\Contracts\HasColor; use Filament\Support\Contracts\HasColor;
use Filament\Support\Contracts\HasDescription; use Filament\Support\Contracts\HasDescription;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\States; namespace Modules\User\App\States;
use A909M\FilamentStateFusion\Concerns\StateFusionInfo; use A909M\FilamentStateFusion\Concerns\StateFusionInfo;
use A909M\FilamentStateFusion\Contracts\HasFilamentStateFusion; use A909M\FilamentStateFusion\Contracts\HasFilamentStateFusion;

View File

@ -0,0 +1,39 @@
<?php
namespace Modules\User\App\Support\Filament;
use A909M\FilamentStateFusion\Forms\Components\StateFusionSelect;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
class UserFormFields
{
public static function name(): TextInput
{
return TextInput::make('name')->required()->maxLength(255);
}
public static function email(): TextInput
{
return TextInput::make('email')->email()->required()->maxLength(255)->unique(ignoreRecord: true);
}
public static function password(callable $requiredCallback): TextInput
{
return TextInput::make('password')
->password()
->required($requiredCallback)
->dehydrateStateUsing(fn ($state) => filled($state) ? bcrypt($state) : null)
->dehydrated(fn ($state) => filled($state));
}
public static function status(): StateFusionSelect
{
return StateFusionSelect::make('status')->required();
}
public static function roles(): Select
{
return Select::make('roles')->multiple()->relationship('roles', 'name')->preload();
}
}

View File

@ -0,0 +1,67 @@
<?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
{
if (! Schema::hasTable('users')) {
Schema::create('users', function (Blueprint $table): void {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('status')->default('active');
$table->string('password');
$table->string('avatar_url')->nullable();
$table->rememberToken();
$table->timestamps();
});
}
if (! Schema::hasTable('profiles')) {
Schema::create('profiles', function (Blueprint $table): void {
$table->id();
$table->foreignId('user_id')->unique()->constrained('users')->cascadeOnDelete();
$table->string('avatar')->nullable();
$table->text('bio')->nullable();
$table->string('phone')->nullable();
$table->string('city')->nullable();
$table->string('country')->nullable();
$table->string('website')->nullable();
$table->boolean('is_verified')->default(false);
$table->timestamps();
});
}
if (! Schema::hasTable('password_reset_tokens')) {
Schema::create('password_reset_tokens', function (Blueprint $table): void {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
if (! Schema::hasTable('sessions')) {
Schema::create('sessions', function (Blueprint $table): void {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
}
public function down(): void
{
Schema::dropIfExists('sessions');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('profiles');
Schema::dropIfExists('users');
}
};

11
Modules/User/module.json Normal file
View File

@ -0,0 +1,11 @@
{
"name": "User",
"alias": "user",
"description": "",
"keywords": [],
"priority": 0,
"providers": [
"Modules\\User\\App\\Providers\\UserServiceProvider"
],
"files": []
}

View File

@ -9,19 +9,19 @@
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg"> <div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<div class="max-w-xl"> <div class="max-w-xl">
@include('profile.partials.update-profile-information-form') @include('user::profile.partials.update-profile-information-form')
</div> </div>
</div> </div>
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg"> <div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<div class="max-w-xl"> <div class="max-w-xl">
@include('profile.partials.update-password-form') @include('user::profile.partials.update-password-form')
</div> </div>
</div> </div>
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg"> <div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<div class="max-w-xl"> <div class="max-w-xl">
@include('profile.partials.delete-user-form') @include('user::profile.partials.delete-user-form')
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,10 @@
<?php
use Illuminate\Support\Facades\Route;
use Modules\User\App\Http\Controllers\ProfileController;
Route::middleware('auth')->prefix('profile')->name('profile.')->group(function () {
Route::get('/', [ProfileController::class, 'edit'])->name('edit');
Route::patch('/', [ProfileController::class, 'update'])->name('update');
Route::delete('/', [ProfileController::class, 'destroy'])->name('destroy');
});

View File

@ -3,7 +3,7 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User; use Modules\User\App\Models\User;
use Illuminate\Auth\Events\PasswordReset; use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -15,19 +15,11 @@ use Illuminate\View\View;
class NewPasswordController extends Controller class NewPasswordController extends Controller
{ {
/**
* Display the password reset view.
*/
public function create(Request $request): View public function create(Request $request): View
{ {
return view('auth.reset-password', ['request' => $request]); return view('auth.reset-password', ['request' => $request]);
} }
/**
* Handle an incoming new password request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse public function store(Request $request): RedirectResponse
{ {
$request->validate([ $request->validate([
@ -36,9 +28,6 @@ class NewPasswordController extends Controller
'password' => ['required', 'confirmed', Rules\Password::defaults()], 'password' => ['required', 'confirmed', Rules\Password::defaults()],
]); ]);
// Here we will attempt to reset the user's password. If it is successful we
// will update the password on an actual user model and persist it to the
// database. Otherwise we will parse the error and return the response.
$status = Password::reset( $status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'), $request->only('email', 'password', 'password_confirmation', 'token'),
function (User $user) use ($request) { function (User $user) use ($request) {
@ -51,9 +40,6 @@ class NewPasswordController extends Controller
} }
); );
// If the password was successfully reset, we will redirect the user back to
// the application's home authenticated view. If there is an error we can
// redirect them back to where they came from with their error message.
return $status == Password::PASSWORD_RESET return $status == Password::PASSWORD_RESET
? redirect()->route('login')->with('status', __($status)) ? redirect()->route('login')->with('status', __($status))
: back()->withInput($request->only('email')) : back()->withInput($request->only('email'))

View File

@ -3,7 +3,7 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User; use Modules\User\App\Models\User;
use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -14,19 +14,11 @@ use Illuminate\View\View;
class RegisteredUserController extends Controller class RegisteredUserController extends Controller
{ {
/**
* Display the registration view.
*/
public function create(): View public function create(): View
{ {
return view('auth.register'); return view('auth.register');
} }
/**
* Handle an incoming registration request.
*
* @throws \Illuminate\Validation\ValidationException
*/
public function store(Request $request): RedirectResponse public function store(Request $request): RedirectResponse
{ {
$request->validate([ $request->validate([

View File

@ -3,7 +3,7 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User; use Modules\User\App\Models\User;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@ -15,9 +15,6 @@ use Throwable;
class SocialAuthController extends Controller class SocialAuthController extends Controller
{ {
/**
* @var array<int, string>
*/
private array $allowedProviders = ['google', 'facebook', 'apple']; private array $allowedProviders = ['google', 'facebook', 'apple'];
public function redirect(string $provider): RedirectResponse public function redirect(string $provider): RedirectResponse

View File

@ -1,104 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Models\Conversation;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Modules\Listing\Models\Listing;
class ConversationController extends Controller
{
public function start(Request $request, Listing $listing): RedirectResponse
{
$user = $request->user();
if (! $listing->user_id) {
return back()->with('error', 'Bu ilan için mesajlaşma açılamadı.');
}
if ((int) $listing->user_id === (int) $user->getKey()) {
return back()->with('error', 'Kendi ilanına mesaj gönderemezsin.');
}
$conversation = Conversation::query()->firstOrCreate(
[
'listing_id' => $listing->getKey(),
'buyer_id' => $user->getKey(),
],
[
'seller_id' => $listing->user_id,
],
);
if ((int) $conversation->seller_id !== (int) $listing->user_id) {
$conversation->forceFill([
'seller_id' => $listing->user_id,
])->save();
}
$user->favoriteListings()->syncWithoutDetaching([$listing->getKey()]);
$messageBody = trim((string) $request->string('message'));
if ($messageBody !== '') {
$message = $conversation->messages()->create([
'sender_id' => $user->getKey(),
'body' => $messageBody,
]);
$conversation->forceFill([
'last_message_at' => $message->created_at,
])->save();
}
return redirect()
->route('panel.inbox.index', array_merge(
$this->inboxFilters($request),
['conversation' => $conversation->getKey()],
))
->with('success', $messageBody !== '' ? 'Mesaj gönderildi.' : 'Sohbet açıldı.');
}
public function send(Request $request, Conversation $conversation): RedirectResponse
{
$user = $request->user();
$userId = (int) $user->getKey();
if ((int) $conversation->buyer_id !== $userId && (int) $conversation->seller_id !== $userId) {
abort(403);
}
$payload = $request->validate([
'message' => ['required', 'string', 'max:2000'],
]);
$message = $conversation->messages()->create([
'sender_id' => $userId,
'body' => trim($payload['message']),
]);
$conversation->forceFill([
'last_message_at' => $message->created_at,
])->save();
return redirect()
->route('panel.inbox.index', array_merge(
$this->inboxFilters($request),
['conversation' => $conversation->getKey()],
))
->with('success', 'Mesaj gönderildi.');
}
private function inboxFilters(Request $request): array
{
$filters = [];
$messageFilter = (string) $request->string('message_filter');
if (in_array($messageFilter, ['all', 'unread', 'important'], true)) {
$filters['message_filter'] = $messageFilter;
}
return $filters;
}
}

View File

@ -4,7 +4,7 @@ namespace App\Http\Controllers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Modules\Listing\Models\Listing; use Modules\Listing\Models\Listing;
use Modules\Category\Models\Category; use Modules\Category\Models\Category;
use App\Models\User; use Modules\User\App\Models\User;
class HomeController extends Controller class HomeController extends Controller
{ {

View File

@ -2,8 +2,6 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\Conversation;
use App\Models\ConversationMessage;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\View\View; use Illuminate\View\View;
@ -59,88 +57,6 @@ class PanelController extends Controller
]); ]);
} }
public function inbox(Request $request): View
{
$userId = (int) $request->user()->getKey();
$messageFilter = (string) $request->string('message_filter', 'all');
if (! in_array($messageFilter, ['all', 'unread', 'important'], true)) {
$messageFilter = 'all';
}
$conversations = Conversation::query()
->forUser($userId)
->when(
in_array($messageFilter, ['unread', 'important'], true),
fn ($query) => $query->whereHas('messages', fn ($messageQuery) => $messageQuery
->where('sender_id', '!=', $userId)
->whereNull('read_at'))
)
->with([
'listing:id,title,price,currency,user_id',
'buyer:id,name',
'seller:id,name',
'lastMessage',
])
->withCount([
'messages as unread_count' => fn ($query) => $query
->where('sender_id', '!=', $userId)
->whereNull('read_at'),
])
->orderByDesc('last_message_at')
->orderByDesc('updated_at')
->get();
$selectedConversation = null;
$selectedConversationId = $request->integer('conversation');
if ($selectedConversationId <= 0 && $conversations->isNotEmpty()) {
$selectedConversationId = (int) $conversations->first()->getKey();
}
if ($selectedConversationId > 0) {
$selectedConversation = $conversations->firstWhere('id', $selectedConversationId);
if ($selectedConversation) {
$selectedConversation->load([
'listing:id,title,price,currency,user_id',
'messages' => fn ($query) => $query
->with('sender:id,name')
->orderBy('created_at'),
]);
ConversationMessage::query()
->where('conversation_id', $selectedConversation->getKey())
->where('sender_id', '!=', $userId)
->whereNull('read_at')
->update([
'read_at' => now(),
'updated_at' => now(),
]);
$conversations = $conversations->map(function (Conversation $conversation) use ($selectedConversation): Conversation {
if ((int) $conversation->getKey() === (int) $selectedConversation->getKey()) {
$conversation->unread_count = 0;
}
return $conversation;
});
}
}
return view('panel.inbox', [
'conversations' => $conversations,
'selectedConversation' => $selectedConversation,
'messageFilter' => $messageFilter,
'quickMessages' => [
'Merhaba',
'İlan hâlâ satışta mı?',
'Son fiyat nedir?',
'Teşekkürler',
],
]);
}
public function destroyListing(Request $request, Listing $listing): RedirectResponse public function destroyListing(Request $request, Listing $listing): RedirectResponse
{ {
$this->guardListingOwner($request, $listing); $this->guardListingOwner($request, $listing);

View File

@ -1,60 +0,0 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ProfileUpdateRequest;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\View\View;
class ProfileController extends Controller
{
/**
* Display the user's profile form.
*/
public function edit(Request $request): View
{
return view('profile.edit', [
'user' => $request->user(),
]);
}
/**
* Update the user's profile information.
*/
public function update(ProfileUpdateRequest $request): RedirectResponse
{
$request->user()->fill($request->validated());
if ($request->user()->isDirty('email')) {
$request->user()->email_verified_at = null;
}
$request->user()->save();
return Redirect::route('profile.edit')->with('status', 'profile-updated');
}
/**
* Delete the user's account.
*/
public function destroy(Request $request): RedirectResponse
{
$request->validateWithBag('userDeletion', [
'password' => ['required', 'current_password'],
]);
$user = $request->user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return Redirect::to('/');
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace App\Http\Requests;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class ProfileUpdateRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'string',
'lowercase',
'email',
'max:255',
Rule::unique(User::class)->ignore($this->user()->id),
],
];
}
}

View File

@ -16,7 +16,7 @@ use Modules\Listing\Support\ListingCustomFieldSchemaBuilder;
use Modules\Listing\Support\ListingPanelHelper; use Modules\Listing\Support\ListingPanelHelper;
use Modules\Location\Models\City; use Modules\Location\Models\City;
use Modules\Location\Models\Country; use Modules\Location\Models\Country;
use Modules\Profile\Models\Profile; use Modules\User\App\Models\Profile;
use Throwable; use Throwable;
class PanelQuickListingForm extends Component class PanelQuickListingForm extends Component

View File

@ -1,76 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Modules\Listing\Models\Listing;
class Conversation extends Model
{
use HasFactory;
protected $fillable = [
'listing_id',
'seller_id',
'buyer_id',
'last_message_at',
];
protected $casts = [
'last_message_at' => 'datetime',
];
public function listing()
{
return $this->belongsTo(Listing::class);
}
public function seller()
{
return $this->belongsTo(User::class, 'seller_id');
}
public function buyer()
{
return $this->belongsTo(User::class, 'buyer_id');
}
public function messages()
{
return $this->hasMany(ConversationMessage::class);
}
public function lastMessage()
{
return $this->hasOne(ConversationMessage::class)
->latestOfMany()
->select([
'conversation_messages.id',
'conversation_messages.conversation_id',
'conversation_messages.sender_id',
'conversation_messages.body',
'conversation_messages.created_at',
]);
}
public function scopeForUser(Builder $query, int $userId): Builder
{
return $query->where(function (Builder $participantQuery) use ($userId): void {
$participantQuery
->where('buyer_id', $userId)
->orWhere('seller_id', $userId);
});
}
public static function buyerListingConversationId(int $listingId, int $buyerId): ?int
{
$value = static::query()
->where('listing_id', $listingId)
->where('buyer_id', $buyerId)
->value('id');
return is_null($value) ? null : (int) $value;
}
}

View File

@ -62,7 +62,7 @@ return [
'providers' => [ 'providers' => [
'users' => [ 'users' => [
'driver' => 'eloquent', 'driver' => 'eloquent',
'model' => env('AUTH_MODEL', App\Models\User::class), 'model' => env('AUTH_MODEL', Modules\User\App\Models\User::class),
], ],
// 'users' => [ // 'users' => [

View File

@ -5,22 +5,14 @@ namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Modules\User\App\Models\User;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory class UserFactory extends Factory
{ {
/** protected $model = User::class;
* The current password being used by the factory.
*/
protected static ?string $password; protected static ?string $password;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array public function definition(): array
{ {
return [ return [
@ -32,9 +24,6 @@ class UserFactory extends Factory
]; ];
} }
/**
* Indicate that the model's email address should be unverified.
*/
public function unverified(): static public function unverified(): static
{ {
return $this->state(fn (array $attributes) => [ return $this->state(fn (array $attributes) => [

View File

@ -1,49 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};

View File

@ -1,34 +0,0 @@
<?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::table('users', function (Blueprint $table): void {
if (! Schema::hasColumn('users', 'avatar_url')) {
$table->string('avatar_url')->nullable()->after('password');
}
if (! Schema::hasColumn('users', 'status')) {
$table->string('status')->default('active')->after('email_verified_at');
}
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table): void {
if (Schema::hasColumn('users', 'avatar_url')) {
$table->dropColumn('avatar_url');
}
if (Schema::hasColumn('users', 'status')) {
$table->dropColumn('status');
}
});
}
};

View File

@ -1,49 +0,0 @@
<?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('favorite_listings', function (Blueprint $table): void {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('listing_id')->constrained('listings')->cascadeOnDelete();
$table->timestamps();
$table->unique(['user_id', 'listing_id']);
});
Schema::create('favorite_sellers', function (Blueprint $table): void {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('seller_id')->constrained('users')->cascadeOnDelete();
$table->timestamps();
$table->unique(['user_id', 'seller_id']);
});
Schema::create('favorite_searches', function (Blueprint $table): void {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('label')->nullable();
$table->string('search_term')->nullable();
$table->foreignId('category_id')->nullable()->constrained('categories')->nullOnDelete();
$table->json('filters')->nullable();
$table->string('signature', 64);
$table->timestamps();
$table->unique(['user_id', 'signature']);
});
}
public function down(): void
{
Schema::dropIfExists('favorite_searches');
Schema::dropIfExists('favorite_sellers');
Schema::dropIfExists('favorite_listings');
}
};

View File

@ -1,42 +0,0 @@
<?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('conversations', function (Blueprint $table): void {
$table->id();
$table->foreignId('listing_id')->constrained('listings')->cascadeOnDelete();
$table->foreignId('seller_id')->constrained('users')->cascadeOnDelete();
$table->foreignId('buyer_id')->constrained('users')->cascadeOnDelete();
$table->timestamp('last_message_at')->nullable();
$table->timestamps();
$table->unique(['listing_id', 'buyer_id']);
$table->index(['seller_id', 'last_message_at']);
$table->index(['buyer_id', 'last_message_at']);
});
Schema::create('conversation_messages', function (Blueprint $table): void {
$table->id();
$table->foreignId('conversation_id')->constrained('conversations')->cascadeOnDelete();
$table->foreignId('sender_id')->constrained('users')->cascadeOnDelete();
$table->text('body');
$table->timestamp('read_at')->nullable();
$table->timestamps();
$table->index(['conversation_id', 'created_at']);
$table->index(['conversation_id', 'read_at']);
});
}
public function down(): void
{
Schema::dropIfExists('conversation_messages');
Schema::dropIfExists('conversations');
}
};

View File

@ -1,7 +1,7 @@
<?php <?php
namespace Database\Seeders; namespace Database\Seeders;
use App\Models\User; use Modules\User\App\Models\User;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Spatie\Permission\Models\Role; use Spatie\Permission\Models\Role;

View File

@ -2,8 +2,10 @@
"Category": true, "Category": true,
"Listing": true, "Listing": true,
"Location": true, "Location": true,
"Profile": true,
"Admin": true, "Admin": true,
"Partner": false, "Partner": false,
"Theme": true "Theme": true,
"Conversation": true,
"Favorite": true,
"User": true
} }

View File

@ -52,9 +52,6 @@ Route::middleware('auth')->group(function () {
Route::put('password', [PasswordController::class, 'update'])->name('password.update'); Route::put('password', [PasswordController::class, 'update'])->name('password.update');
Route::patch('profile', [\App\Http\Controllers\Auth\ProfileController::class, 'update'])->name('profile.update');
Route::delete('profile', [\App\Http\Controllers\Auth\ProfileController::class, 'destroy'])->name('profile.destroy');
Route::post('logout', [AuthenticatedSessionController::class, 'destroy']) Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])
->name('logout'); ->name('logout');
}); });

View File

@ -1,7 +1,5 @@
<?php <?php
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ConversationController;
use App\Http\Controllers\FavoriteController;
use App\Http\Controllers\HomeController; use App\Http\Controllers\HomeController;
use App\Http\Controllers\LanguageController; use App\Http\Controllers\LanguageController;
use App\Http\Controllers\PanelController; use App\Http\Controllers\PanelController;
@ -18,7 +16,6 @@ Route::middleware('auth')->prefix('panel')->name('panel.')->group(function () {
Route::get('/', [PanelController::class, 'index'])->name('index'); Route::get('/', [PanelController::class, 'index'])->name('index');
Route::get('/ilanlarim', [PanelController::class, 'listings'])->name('listings.index'); Route::get('/ilanlarim', [PanelController::class, 'listings'])->name('listings.index');
Route::get('/ilan-ver', [PanelController::class, 'create'])->name('listings.create'); Route::get('/ilan-ver', [PanelController::class, 'create'])->name('listings.create');
Route::get('/gelen-kutusu', [PanelController::class, 'inbox'])->name('inbox.index');
Route::post('/ilanlarim/{listing}/kaldir', [PanelController::class, 'destroyListing'])->name('listings.destroy'); Route::post('/ilanlarim/{listing}/kaldir', [PanelController::class, 'destroyListing'])->name('listings.destroy');
Route::post('/ilanlarim/{listing}/satildi', [PanelController::class, 'markListingAsSold'])->name('listings.mark-sold'); Route::post('/ilanlarim/{listing}/satildi', [PanelController::class, 'markListingAsSold'])->name('listings.mark-sold');
Route::post('/ilanlarim/{listing}/yeniden-yayinla', [PanelController::class, 'republishListing'])->name('listings.republish'); Route::post('/ilanlarim/{listing}/yeniden-yayinla', [PanelController::class, 'republishListing'])->name('listings.republish');
@ -27,17 +24,4 @@ Route::middleware('auth')->prefix('panel')->name('panel.')->group(function () {
Route::get('/partner/{any?}', fn () => redirect()->route('panel.listings.index')) Route::get('/partner/{any?}', fn () => redirect()->route('panel.listings.index'))
->where('any', '.*'); ->where('any', '.*');
Route::middleware('auth')->prefix('favorites')->name('favorites.')->group(function () {
Route::get('/', [FavoriteController::class, 'index'])->name('index');
Route::post('/listings/{listing}/toggle', [FavoriteController::class, 'toggleListing'])->name('listings.toggle');
Route::post('/sellers/{seller}/toggle', [FavoriteController::class, 'toggleSeller'])->name('sellers.toggle');
Route::post('/searches', [FavoriteController::class, 'storeSearch'])->name('searches.store');
Route::delete('/searches/{favoriteSearch}', [FavoriteController::class, 'destroySearch'])->name('searches.destroy');
});
Route::middleware('auth')->name('conversations.')->group(function () {
Route::post('/listings/{listing}/conversation', [ConversationController::class, 'start'])->name('start');
Route::post('/conversations/{conversation}/messages', [ConversationController::class, 'send'])->name('messages.send');
});
require __DIR__.'/auth.php'; require __DIR__.'/auth.php';