Save workspace changes
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Dashboard;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Dashboard\ArtworkEditRequest;
|
||||
use App\Http\Requests\Dashboard\ArtworkDestroyRequest;
|
||||
use App\Http\Requests\Dashboard\UpdateArtworkRequest;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ArtworkController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$artworks = $request->user()
|
||||
->artworks()
|
||||
->latest()
|
||||
->paginate(20);
|
||||
|
||||
return view('artworks.index', [
|
||||
'artworks' => $artworks,
|
||||
'page_title' => 'My Artworks',
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(ArtworkEditRequest $request, int $id): View
|
||||
{
|
||||
$artwork = $request->artwork();
|
||||
|
||||
return view('artworks.edit', [
|
||||
'artwork' => $artwork,
|
||||
'page_title' => 'Edit Artwork',
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateArtworkRequest $request, int $id): RedirectResponse
|
||||
{
|
||||
$artwork = $request->artwork();
|
||||
|
||||
$data = $request->validated();
|
||||
|
||||
$artwork->title = $data['title'];
|
||||
$artwork->description = $data['description'] ?? null;
|
||||
|
||||
if ($request->hasFile('file')) {
|
||||
$file = $request->file('file');
|
||||
|
||||
// Remove prior stored file if it's on the public disk.
|
||||
if (! empty($artwork->file_path) && Storage::disk('public')->exists($artwork->file_path)) {
|
||||
Storage::disk('public')->delete($artwork->file_path);
|
||||
}
|
||||
|
||||
$size = $file->getSize() ?? 0;
|
||||
$mime = $file->getMimeType() ?? 'application/octet-stream';
|
||||
|
||||
$dimensions = @getimagesize($file->getRealPath());
|
||||
$width = is_array($dimensions) ? (int) ($dimensions[0] ?? 0) : 0;
|
||||
$height = is_array($dimensions) ? (int) ($dimensions[1] ?? 0) : 0;
|
||||
|
||||
$path = $file->storePublicly('artworks', 'public');
|
||||
|
||||
$artwork->file_name = $file->getClientOriginalName();
|
||||
$artwork->file_path = $path;
|
||||
$artwork->file_size = (int) $size;
|
||||
$artwork->mime_type = (string) $mime;
|
||||
$artwork->width = max(1, $width);
|
||||
$artwork->height = max(1, $height);
|
||||
|
||||
// If a file is replaced, clear CDN-derived fields unless a separate pipeline repopulates them.
|
||||
$artwork->hash = null;
|
||||
$artwork->thumb_ext = null;
|
||||
$artwork->file_ext = $file->guessExtension() ?: $file->getClientOriginalExtension() ?: null;
|
||||
}
|
||||
|
||||
$artwork->save();
|
||||
|
||||
return redirect()
|
||||
->route('dashboard.artworks.edit', $artwork->id)
|
||||
->with('status', 'Artwork updated.');
|
||||
}
|
||||
|
||||
public function destroy(ArtworkDestroyRequest $request, int $id): RedirectResponse
|
||||
{
|
||||
$artwork = $request->artwork();
|
||||
|
||||
// Best-effort remove stored file.
|
||||
if (! empty($artwork->file_path) && Storage::disk('public')->exists($artwork->file_path)) {
|
||||
Storage::disk('public')->delete($artwork->file_path);
|
||||
}
|
||||
|
||||
$artwork->delete();
|
||||
|
||||
return redirect()
|
||||
->route('dashboard.artworks.index')
|
||||
->with('status', 'Artwork deleted.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Dashboard;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\ReceivedCommentsInboxService;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CommentController extends Controller
|
||||
{
|
||||
public function __construct(private readonly ReceivedCommentsInboxService $inbox) {}
|
||||
|
||||
public function received(Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$search = trim((string) $request->query('q', ''));
|
||||
$sort = strtolower((string) $request->query('sort', 'newest'));
|
||||
|
||||
if (! in_array($sort, ['newest', 'oldest'], true)) {
|
||||
$sort = 'newest';
|
||||
}
|
||||
|
||||
$baseQuery = $this->inbox->queryForUser($user)
|
||||
->with(['user.profile', 'artwork']);
|
||||
|
||||
if ($search !== '') {
|
||||
$baseQuery->where(function ($query) use ($search): void {
|
||||
$query->where('content', 'like', '%' . $search . '%')
|
||||
->orWhere('raw_content', 'like', '%' . $search . '%')
|
||||
->orWhereHas('artwork', function ($artworkQuery) use ($search): void {
|
||||
$artworkQuery->where('title', 'like', '%' . $search . '%')
|
||||
->orWhere('slug', 'like', '%' . $search . '%');
|
||||
})
|
||||
->orWhereHas('user', function ($userQuery) use ($search): void {
|
||||
$userQuery->where('username', 'like', '%' . $search . '%')
|
||||
->orWhere('name', 'like', '%' . $search . '%');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$orderedQuery = (clone $baseQuery)
|
||||
->orderBy('created_at', $sort === 'oldest' ? 'asc' : 'desc');
|
||||
|
||||
$comments = $orderedQuery->paginate(12)->withQueryString();
|
||||
|
||||
$statsBaseQuery = clone $baseQuery;
|
||||
$freshlyClearedCount = $this->inbox->unreadCountForUser($user);
|
||||
$totalComments = (clone $statsBaseQuery)->count();
|
||||
$recentComments = (clone $statsBaseQuery)->where('created_at', '>=', now()->subDays(7))->count();
|
||||
$uniqueCommenters = (clone $statsBaseQuery)->distinct('user_id')->count('user_id');
|
||||
$activeArtworks = (clone $statsBaseQuery)->distinct('artwork_id')->count('artwork_id');
|
||||
|
||||
$this->inbox->markInboxRead($user);
|
||||
|
||||
return view('dashboard.comments', [
|
||||
'comments' => $comments,
|
||||
'search' => $search,
|
||||
'sort' => $sort,
|
||||
'freshlyClearedCount' => $freshlyClearedCount,
|
||||
'stats' => [
|
||||
'total' => $totalComments,
|
||||
'recent' => $recentComments,
|
||||
'commenters' => $uniqueCommenters,
|
||||
'artworks' => $activeArtworks,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Dashboard;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Artwork;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class DashboardAwardsController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$artworks = Artwork::query()
|
||||
->where('user_id', (int) $user->id)
|
||||
->whereHas('awards')
|
||||
->with(['awardStat', 'stats', 'categories.contentType'])
|
||||
->orderByDesc(
|
||||
\App\Models\ArtworkAwardStat::select('score_total')
|
||||
->whereColumn('artwork_id', 'artworks.id')
|
||||
->limit(1)
|
||||
)
|
||||
->paginate(24)
|
||||
->withQueryString();
|
||||
|
||||
return view('dashboard.awards', [
|
||||
'artworks' => $artworks,
|
||||
'page_title' => 'My Awards – SkinBase',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Dashboard;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Artwork;
|
||||
use App\Models\ContentType;
|
||||
use App\Support\AvatarUrl;
|
||||
use App\Services\ThumbnailPresenter;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class DashboardGalleryController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$perPage = 24;
|
||||
|
||||
$query = Artwork::query()
|
||||
->with([
|
||||
'user:id,name,username',
|
||||
'user.profile:user_id,avatar_hash',
|
||||
'categories:id,name,slug,content_type_id,parent_id,sort_order',
|
||||
'categories.contentType:id,name,slug',
|
||||
])
|
||||
->where('user_id', (int) $user->id)
|
||||
->orderBy('published_at', 'desc');
|
||||
|
||||
$artworks = $query->paginate($perPage)->withQueryString();
|
||||
$artworks->setCollection(
|
||||
$artworks->getCollection()->map(fn (Artwork $artwork) => $this->presentArtwork($artwork))
|
||||
);
|
||||
|
||||
$mainCategories = ContentType::ordered()
|
||||
->get(['name', 'slug'])
|
||||
->map(function (ContentType $type) {
|
||||
return (object) [
|
||||
'id' => $type->id,
|
||||
'name' => $type->name,
|
||||
'slug' => $type->slug,
|
||||
'url' => '/' . strtolower($type->slug),
|
||||
];
|
||||
});
|
||||
|
||||
return view('gallery.index', [
|
||||
'gallery_type' => 'dashboard',
|
||||
'mainCategories' => $mainCategories,
|
||||
'subcategories' => $mainCategories,
|
||||
'contentType' => null,
|
||||
'category' => null,
|
||||
'artworks' => $artworks,
|
||||
'hero_title' => 'My Gallery',
|
||||
'hero_description' => 'Your uploaded artworks.',
|
||||
'breadcrumbs' => collect(),
|
||||
'page_title' => 'My Gallery - SkinBase',
|
||||
'page_meta_description' => 'My uploaded artworks on SkinBase',
|
||||
'page_meta_keywords' => 'my gallery, uploads, skinbase',
|
||||
'page_canonical' => url('/dashboard/gallery'),
|
||||
]);
|
||||
}
|
||||
|
||||
private function presentArtwork(Artwork $artwork): object
|
||||
{
|
||||
$primary = $artwork->categories->sortBy('sort_order')->first();
|
||||
$present = ThumbnailPresenter::present($artwork, 'md');
|
||||
$group = $artwork->group;
|
||||
$isGroupPublisher = $group !== null;
|
||||
$displayName = $isGroupPublisher ? ($group->name ?? 'Skinbase') : ($artwork->user?->name ?? 'Skinbase');
|
||||
$username = $isGroupPublisher ? '' : ($artwork->user?->username ?? '');
|
||||
$avatarUrl = $isGroupPublisher
|
||||
? $group->avatarUrl()
|
||||
: AvatarUrl::forUser(
|
||||
(int) ($artwork->user_id ?? 0),
|
||||
$artwork->user?->profile?->avatar_hash ?? null,
|
||||
64
|
||||
);
|
||||
$profileUrl = $isGroupPublisher ? $group->publicUrl() : ($username !== '' ? '/@' . $username : null);
|
||||
|
||||
return (object) [
|
||||
'id' => $artwork->id,
|
||||
'name' => $artwork->title,
|
||||
'content_type_name' => $primary?->contentType?->name ?? '',
|
||||
'content_type_slug' => $primary?->contentType?->slug ?? '',
|
||||
'category_name' => $primary?->name ?? '',
|
||||
'category_slug' => $primary?->slug ?? '',
|
||||
'thumb_url' => $present['url'],
|
||||
'thumb_srcset' => $present['srcset'] ?? $present['url'],
|
||||
'uname' => $displayName,
|
||||
'username' => $username,
|
||||
'avatar_url' => $avatarUrl,
|
||||
'profile_url' => $profileUrl,
|
||||
'published_as_type' => $isGroupPublisher ? 'group' : 'user',
|
||||
'publisher' => [
|
||||
'type' => $isGroupPublisher ? 'group' : 'user',
|
||||
'name' => $displayName,
|
||||
'username' => $username,
|
||||
'avatar_url' => $avatarUrl,
|
||||
'profile_url' => $profileUrl,
|
||||
],
|
||||
'published_at' => $artwork->published_at,
|
||||
'slug' => $artwork->slug ?? '',
|
||||
'width' => $artwork->width ?? null,
|
||||
'height' => $artwork->height ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Dashboard;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\DashboardPreference;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DashboardPreferenceController extends Controller
|
||||
{
|
||||
public function updateShortcuts(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'pinned_spaces' => ['present', 'array', 'max:' . DashboardPreference::MAX_PINNED_SPACES],
|
||||
'pinned_spaces.*' => ['string'],
|
||||
]);
|
||||
|
||||
$pinnedSpaces = DashboardPreference::sanitizePinnedSpaces($validated['pinned_spaces'] ?? []);
|
||||
|
||||
DashboardPreference::query()->updateOrCreate(
|
||||
['user_id' => $request->user()->id],
|
||||
['pinned_spaces' => $pinnedSpaces]
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'data' => [
|
||||
'pinned_spaces' => $pinnedSpaces,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Dashboard;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Artwork;
|
||||
use App\Services\UserStatsService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\View\View;
|
||||
use App\Support\AvatarUrl;
|
||||
|
||||
class FavoriteController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$user = $request->user();
|
||||
$perPage = 20;
|
||||
|
||||
$favTable = 'artwork_favourites';
|
||||
$sort = $request->query('sort', 'newest');
|
||||
$order = $sort === 'oldest' ? 'asc' : 'desc';
|
||||
$orderColumn = 'created_at';
|
||||
|
||||
$query = DB::table($favTable)->where('user_id', (int) $user->id);
|
||||
if ($orderColumn) {
|
||||
$query = $query->orderBy($orderColumn, $order);
|
||||
}
|
||||
|
||||
// Collect artwork ids in the correct order using the favourites table
|
||||
$artworkIds = $query->pluck('artwork_id')->values()->all();
|
||||
|
||||
$page = max(1, (int) $request->query('page', 1));
|
||||
$slice = array_slice($artworkIds, ($page - 1) * $perPage, $perPage);
|
||||
|
||||
$artworks = collect();
|
||||
if ($slice !== []) {
|
||||
$arts = Artwork::query()
|
||||
->whereIn('id', $slice)
|
||||
->with(['user.profile', 'categories'])
|
||||
->withCount(['favourites', 'comments'])
|
||||
->get()
|
||||
->keyBy('id');
|
||||
|
||||
foreach ($slice as $id) {
|
||||
$a = $arts->get($id);
|
||||
if (! $a) continue;
|
||||
|
||||
$primaryCategory = $a->categories->sortBy('sort_order')->first();
|
||||
$username = $a->user?->username ?? $a->user?->name ?? '';
|
||||
|
||||
$artworks->push((object) [
|
||||
'id' => $a->id,
|
||||
'name' => $a->title,
|
||||
'title' => $a->title,
|
||||
'thumb' => $a->thumbUrl('md') ?? $a->thumbnail_url ?? null,
|
||||
'thumb_url' => $a->thumbUrl('md') ?? $a->thumbnail_url ?? null,
|
||||
'slug' => $a->slug,
|
||||
'author' => $username,
|
||||
'uname' => $username,
|
||||
'username' => $a->user?->username ?? '',
|
||||
'avatar_url' => AvatarUrl::forUser(
|
||||
(int) ($a->user_id ?? 0),
|
||||
$a->user?->profile?->avatar_hash ?? null,
|
||||
64
|
||||
),
|
||||
'content_type_name' => $primaryCategory?->contentType?->name ?? '',
|
||||
'content_type_slug' => $primaryCategory?->contentType?->slug ?? '',
|
||||
'category_name' => $primaryCategory->name ?? '',
|
||||
'category_slug' => $primaryCategory->slug ?? '',
|
||||
'width' => $a->width,
|
||||
'height' => $a->height,
|
||||
'likes' => (int) ($a->favourites_count ?? $a->likes ?? 0),
|
||||
'comments_count' => (int) ($a->comments_count ?? 0),
|
||||
'published_at' => $a->published_at,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$paginator = new LengthAwarePaginator($artworks->toArray(), count($artworkIds), $perPage, $page, ['path' => $request->url(), 'query' => $request->query()]);
|
||||
|
||||
return view('dashboard.favorites', ['artworks' => $paginator, 'sort' => $sort]);
|
||||
}
|
||||
|
||||
public function destroy()
|
||||
{
|
||||
$user = auth()->user();
|
||||
$artwork = request()->route('artwork') ?? request()->input('artwork');
|
||||
if (! $artwork) {
|
||||
$last = collect(request()->segments())->last();
|
||||
if (is_numeric($last)) {
|
||||
$artwork = (int) $last;
|
||||
}
|
||||
}
|
||||
$artworkId = is_object($artwork) ? (int) $artwork->id : (int) $artwork;
|
||||
Log::info('FavoriteController::destroy', ['user_id' => $user->id ?? null, 'artworkId' => $artworkId]);
|
||||
// Look up creator before deleting so we can decrement their counter
|
||||
$creatorId = (int) DB::table('artworks')->where('id', $artworkId)->value('user_id');
|
||||
|
||||
DB::table('artwork_favourites')
|
||||
->where('user_id', (int) $user->id)
|
||||
->where('artwork_id', $artworkId)
|
||||
->delete();
|
||||
|
||||
if ($creatorId) {
|
||||
app(UserStatsService::class)->decrementFavoritesReceived($creatorId);
|
||||
}
|
||||
|
||||
return redirect()->route('dashboard.favorites')->with('status', 'favourite-removed');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Dashboard;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\AvatarUrl;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class FollowerController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
$perPage = 30;
|
||||
$search = trim((string) $request->query('q', ''));
|
||||
$sort = (string) $request->query('sort', 'recent');
|
||||
$relationship = (string) $request->query('relationship', 'all');
|
||||
|
||||
$allowedSorts = ['recent', 'oldest', 'name', 'uploads', 'followers'];
|
||||
$allowedRelationships = ['all', 'following-back', 'not-followed'];
|
||||
|
||||
if (! in_array($sort, $allowedSorts, true)) {
|
||||
$sort = 'recent';
|
||||
}
|
||||
|
||||
if (! in_array($relationship, $allowedRelationships, true)) {
|
||||
$relationship = 'all';
|
||||
}
|
||||
|
||||
// People who follow $user (user_id = $user being followed)
|
||||
$baseQuery = DB::table('user_followers as uf')
|
||||
->join('users as u', 'u.id', '=', 'uf.follower_id')
|
||||
->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
|
||||
->leftJoin('user_statistics as us', 'us.user_id', '=', 'u.id')
|
||||
->leftJoin('user_followers as mutual', function ($join) use ($user): void {
|
||||
$join->on('mutual.user_id', '=', 'uf.follower_id')
|
||||
->where('mutual.follower_id', '=', $user->id);
|
||||
})
|
||||
->where('uf.user_id', $user->id)
|
||||
->whereNull('u.deleted_at')
|
||||
->when($search !== '', function ($query) use ($search): void {
|
||||
$query->where(function ($inner) use ($search): void {
|
||||
$inner->where('u.username', 'like', '%' . $search . '%')
|
||||
->orWhere('u.name', 'like', '%' . $search . '%');
|
||||
});
|
||||
})
|
||||
->when($relationship === 'following-back', function ($query): void {
|
||||
$query->whereNotNull('mutual.created_at');
|
||||
})
|
||||
->when($relationship === 'not-followed', function ($query): void {
|
||||
$query->whereNull('mutual.created_at');
|
||||
});
|
||||
|
||||
$summaryBaseQuery = clone $baseQuery;
|
||||
|
||||
$followers = $baseQuery
|
||||
->when($sort === 'recent', fn ($query) => $query->orderByDesc('uf.created_at'))
|
||||
->when($sort === 'oldest', fn ($query) => $query->orderBy('uf.created_at'))
|
||||
->when($sort === 'name', fn ($query) => $query->orderByRaw('COALESCE(u.username, u.name) asc'))
|
||||
->when($sort === 'uploads', fn ($query) => $query->orderByDesc('us.uploads_count')->orderByRaw('COALESCE(u.username, u.name) asc'))
|
||||
->when($sort === 'followers', fn ($query) => $query->orderByDesc('us.followers_count')->orderByRaw('COALESCE(u.username, u.name) asc'))
|
||||
->select([
|
||||
'u.id', 'u.username', 'u.name',
|
||||
'up.avatar_hash',
|
||||
'us.uploads_count',
|
||||
'us.followers_count',
|
||||
'uf.created_at as followed_at',
|
||||
'mutual.created_at as followed_back_at',
|
||||
])
|
||||
->paginate($perPage)
|
||||
->withQueryString()
|
||||
->through(fn ($row) => (object) [
|
||||
'id' => $row->id,
|
||||
'name' => $row->name,
|
||||
'username' => $row->username,
|
||||
'uname' => $row->username ?? $row->name,
|
||||
'avatar_url' => AvatarUrl::forUser((int) $row->id, $row->avatar_hash, 64),
|
||||
'profile_url' => '/@' . strtolower((string) ($row->username ?? $row->id)),
|
||||
'uploads' => $row->uploads_count ?? 0,
|
||||
'followers_count' => $row->followers_count ?? 0,
|
||||
'is_following_back' => $row->followed_back_at !== null,
|
||||
'followed_back_at' => $row->followed_back_at,
|
||||
'followed_at' => $row->followed_at,
|
||||
]);
|
||||
|
||||
$summary = [
|
||||
'total_followers' => (clone $summaryBaseQuery)->count(),
|
||||
'following_back' => (clone $summaryBaseQuery)->whereNotNull('mutual.created_at')->count(),
|
||||
'not_followed' => (clone $summaryBaseQuery)->whereNull('mutual.created_at')->count(),
|
||||
];
|
||||
|
||||
return view('dashboard.followers', [
|
||||
'followers' => $followers,
|
||||
'filters' => [
|
||||
'q' => $search,
|
||||
'sort' => $sort,
|
||||
'relationship' => $relationship,
|
||||
],
|
||||
'summary' => $summary,
|
||||
'page_title' => 'My Followers',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Dashboard;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Support\AvatarUrl;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class FollowingController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = $request->user();
|
||||
$perPage = 30;
|
||||
$search = trim((string) $request->query('q', ''));
|
||||
$sort = (string) $request->query('sort', 'recent');
|
||||
$relationship = (string) $request->query('relationship', 'all');
|
||||
|
||||
$allowedSorts = ['recent', 'oldest', 'name', 'uploads', 'followers'];
|
||||
$allowedRelationships = ['all', 'mutual', 'one-way'];
|
||||
|
||||
if (! in_array($sort, $allowedSorts, true)) {
|
||||
$sort = 'recent';
|
||||
}
|
||||
|
||||
if (! in_array($relationship, $allowedRelationships, true)) {
|
||||
$relationship = 'all';
|
||||
}
|
||||
|
||||
// People that $user follows (follower_id = $user)
|
||||
$baseQuery = DB::table('user_followers as uf')
|
||||
->join('users as u', 'u.id', '=', 'uf.user_id')
|
||||
->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
|
||||
->leftJoin('user_statistics as us', 'us.user_id', '=', 'u.id')
|
||||
->leftJoin('user_followers as mutual', function ($join) use ($user): void {
|
||||
$join->on('mutual.follower_id', '=', 'uf.user_id')
|
||||
->where('mutual.user_id', '=', $user->id);
|
||||
})
|
||||
->where('uf.follower_id', $user->id)
|
||||
->whereNull('u.deleted_at')
|
||||
->when($search !== '', function ($query) use ($search): void {
|
||||
$query->where(function ($inner) use ($search): void {
|
||||
$inner->where('u.username', 'like', '%' . $search . '%')
|
||||
->orWhere('u.name', 'like', '%' . $search . '%');
|
||||
});
|
||||
})
|
||||
->when($relationship === 'mutual', function ($query): void {
|
||||
$query->whereNotNull('mutual.created_at');
|
||||
})
|
||||
->when($relationship === 'one-way', function ($query): void {
|
||||
$query->whereNull('mutual.created_at');
|
||||
});
|
||||
|
||||
$summaryBaseQuery = clone $baseQuery;
|
||||
|
||||
$following = $baseQuery
|
||||
->when($sort === 'recent', fn ($query) => $query->orderByDesc('uf.created_at'))
|
||||
->when($sort === 'oldest', fn ($query) => $query->orderBy('uf.created_at'))
|
||||
->when($sort === 'name', fn ($query) => $query->orderByRaw('COALESCE(u.username, u.name) asc'))
|
||||
->when($sort === 'uploads', fn ($query) => $query->orderByDesc('us.uploads_count')->orderByRaw('COALESCE(u.username, u.name) asc'))
|
||||
->when($sort === 'followers', fn ($query) => $query->orderByDesc('us.followers_count')->orderByRaw('COALESCE(u.username, u.name) asc'))
|
||||
->select([
|
||||
'u.id', 'u.username', 'u.name',
|
||||
'up.avatar_hash',
|
||||
'us.uploads_count',
|
||||
'us.followers_count',
|
||||
'uf.created_at as followed_at',
|
||||
'mutual.created_at as follows_you_at',
|
||||
])
|
||||
->paginate($perPage)
|
||||
->withQueryString()
|
||||
->through(fn ($row) => (object) [
|
||||
'id' => $row->id,
|
||||
'username' => $row->username,
|
||||
'name' => $row->name,
|
||||
'uname' => $row->username ?? $row->name,
|
||||
'avatar_url' => AvatarUrl::forUser((int) $row->id, $row->avatar_hash, 64),
|
||||
'profile_url' => '/@' . strtolower((string) ($row->username ?? $row->id)),
|
||||
'uploads' => $row->uploads_count ?? 0,
|
||||
'followers_count' => $row->followers_count ?? 0,
|
||||
'follows_you' => $row->follows_you_at !== null,
|
||||
'follows_you_at' => $row->follows_you_at,
|
||||
'followed_at' => $row->followed_at,
|
||||
]);
|
||||
|
||||
$summary = [
|
||||
'total_following' => (clone $summaryBaseQuery)->count(),
|
||||
'mutual' => (clone $summaryBaseQuery)->whereNotNull('mutual.created_at')->count(),
|
||||
'one_way' => (clone $summaryBaseQuery)->whereNull('mutual.created_at')->count(),
|
||||
];
|
||||
|
||||
return view('dashboard.following', [
|
||||
'following' => $following,
|
||||
'filters' => [
|
||||
'q' => $search,
|
||||
'sort' => $sort,
|
||||
'relationship' => $relationship,
|
||||
],
|
||||
'summary' => $summary,
|
||||
'page_title' => 'People I Follow',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Dashboard;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Manage\ManageArtworkEditRequest;
|
||||
use App\Http\Requests\Manage\ManageArtworkUpdateRequest;
|
||||
use App\Http\Requests\Manage\ManageArtworkDestroyRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ManageController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$userId = $request->user()->id;
|
||||
$perPage = 50;
|
||||
|
||||
$categorySub = DB::table('artwork_category as ac')
|
||||
->join('categories as c', 'ac.category_id', '=', 'c.id')
|
||||
->select('ac.artwork_id', DB::raw('MIN(c.name) as category_name'))
|
||||
->groupBy('ac.artwork_id');
|
||||
|
||||
$query = DB::table('artworks as a')
|
||||
->leftJoinSub($categorySub, 'cat', function ($join) {
|
||||
$join->on('a.id', '=', 'cat.artwork_id');
|
||||
})
|
||||
->leftJoin('artwork_stats as s', 'a.id', '=', 's.artwork_id')
|
||||
->where('a.user_id', $userId)
|
||||
->select(
|
||||
'a.*',
|
||||
DB::raw('cat.category_name as category_name'),
|
||||
DB::raw('COALESCE(s.rating_count, 0) as rating_num'),
|
||||
DB::raw('COALESCE(s.rating_avg, 0) as rating'),
|
||||
DB::raw('COALESCE(s.downloads, 0) as dls'),
|
||||
DB::raw('COALESCE(s.favorites, 0) as zoom'),
|
||||
DB::raw('COALESCE(s.views, 0) as views')
|
||||
)
|
||||
->orderByDesc('a.published_at')
|
||||
->orderByDesc('a.id');
|
||||
|
||||
$artworks = $query->paginate($perPage);
|
||||
|
||||
return view('manage.index', [
|
||||
'artworks' => $artworks,
|
||||
'page_title' => 'Artwork Manager',
|
||||
]);
|
||||
}
|
||||
|
||||
public function edit(ManageArtworkEditRequest $request, $id)
|
||||
{
|
||||
$artwork = $request->artwork();
|
||||
|
||||
$selectedCategory = DB::table('artwork_category')->where('artwork_id', (int)$id)->value('category_id');
|
||||
$artwork->category = $selectedCategory;
|
||||
|
||||
$categories = DB::table('categories')
|
||||
->where('content_type_id', 0)
|
||||
->orderBy('id')
|
||||
->select(DB::raw('id as category_id'), DB::raw('name as category_name'))
|
||||
->get();
|
||||
|
||||
return view('manage.edit', [
|
||||
'artwork' => $artwork,
|
||||
'categories' => $categories,
|
||||
'page_title' => 'Edit Artwork: ' . ($artwork->title ?? ''),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(ManageArtworkUpdateRequest $request, $id)
|
||||
{
|
||||
$existing = $request->artwork();
|
||||
$data = $request->validated();
|
||||
$update = [
|
||||
'title' => $data['title'],
|
||||
'description' => $data['description'] ?? $existing->description,
|
||||
'updated' => now(),
|
||||
];
|
||||
|
||||
if ($request->hasFile('artwork')) {
|
||||
$file = $request->file('artwork');
|
||||
$path = $file->store('public/uploads/artworks');
|
||||
$filename = basename($path);
|
||||
$update['picture'] = $filename;
|
||||
}
|
||||
|
||||
if ($request->hasFile('attachment')) {
|
||||
$att = $request->file('attachment');
|
||||
$attPath = $att->store('public/uploads/attachments');
|
||||
$update['fname'] = basename($attPath);
|
||||
}
|
||||
|
||||
DB::table('artworks')->where('id', (int)$id)->update($update);
|
||||
|
||||
if (isset($data['section'])) {
|
||||
DB::table('artwork_category')->where('artwork_id', (int)$id)->delete();
|
||||
DB::table('artwork_category')->insert([
|
||||
'artwork_id' => (int)$id,
|
||||
'category_id' => (int)$data['section'],
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('manage')->with('status', 'Artwork was successfully updated.');
|
||||
}
|
||||
|
||||
public function destroy(ManageArtworkDestroyRequest $request, $id)
|
||||
{
|
||||
$artwork = $request->artwork();
|
||||
|
||||
if (!empty($artwork->fname)) {
|
||||
Storage::delete('public/uploads/attachments/' . $artwork->fname);
|
||||
}
|
||||
if (!empty($artwork->picture)) {
|
||||
Storage::delete('public/uploads/artworks/' . $artwork->picture);
|
||||
}
|
||||
|
||||
DB::table('artworks')->where('id', (int)$id)->delete();
|
||||
|
||||
return redirect()->route('manage')->with('status', 'Artwork deleted.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Dashboard;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\NotificationService;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class NotificationController extends Controller
|
||||
{
|
||||
public function __construct(private readonly NotificationService $notifications) {}
|
||||
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$page = max(1, (int) $request->query('page', 1));
|
||||
$payload = $this->notifications->listForUser($request->user(), $page, 15);
|
||||
|
||||
return view('dashboard.notifications', [
|
||||
'notifications' => collect($payload['data'] ?? []),
|
||||
'notificationsMeta' => $payload['meta'] ?? [],
|
||||
'unreadCount' => (int) ($payload['unread_count'] ?? 0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user