Allow artworks to have one primary category and extra secondaries.
Keep artwork_category as membership, store a deterministic primary_category_id, and let upload plus studio pick up to five additional same-type categories without changing canonical URLs.
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\World;
|
||||
use App\Models\WorldRelation;
|
||||
use App\Models\WorldSubmission;
|
||||
use App\Models\World;
|
||||
use App\Services\ArtworkEvolutionService;
|
||||
use App\Services\ContentSanitizer;
|
||||
use App\Services\Maturity\ArtworkMaturityService;
|
||||
@@ -12,6 +13,7 @@ use App\Services\Worlds\WorldRewardService;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ArtworkResource extends JsonResource
|
||||
{
|
||||
@@ -23,7 +25,7 @@ class ArtworkResource extends JsonResource
|
||||
*/
|
||||
public function toArray($request): array
|
||||
{
|
||||
$this->resource->loadMissing(['group', 'uploadedBy.profile', 'primaryAuthor.profile', 'contributors.user.profile', 'worldSubmissions.world', 'worldRewardGrants.world']);
|
||||
$this->resource->loadMissing(['group', 'uploadedBy.profile', 'primaryAuthor.profile', 'contributors.user.profile', 'worldSubmissions.world']);
|
||||
|
||||
$md = ThumbnailPresenter::present($this->resource, 'md');
|
||||
$lg = ThumbnailPresenter::present($this->resource, 'lg');
|
||||
@@ -31,18 +33,16 @@ class ArtworkResource extends JsonResource
|
||||
$sq = ThumbnailPresenter::present($this->resource, 'sq');
|
||||
$screenshots = $this->resolveScreenshotAssets();
|
||||
|
||||
$canonicalSlug = \Illuminate\Support\Str::slug((string) ($this->slug ?: $this->title));
|
||||
$canonicalSlug = Str::slug((string) ($this->slug ?: $this->title));
|
||||
if ($canonicalSlug === '') {
|
||||
$canonicalSlug = (string) $this->id;
|
||||
}
|
||||
|
||||
$followerCount = 0;
|
||||
if (!empty($this->user?->id)) {
|
||||
if ($this->hasTable('user_statistics')) {
|
||||
$followerCount = (int) DB::table('user_statistics')
|
||||
->where('user_id', (int) $this->user->id)
|
||||
->value('followers_count');
|
||||
}
|
||||
if (! empty($this->user?->id)) {
|
||||
$followerCount = (int) DB::table('user_statistics')
|
||||
->where('user_id', (int) $this->user->id)
|
||||
->value('followers_count');
|
||||
|
||||
// Legacy fallback for environments where new tables are unavailable.
|
||||
if (($followerCount <= 0) && $this->hasTable('friends_list')) {
|
||||
@@ -61,9 +61,9 @@ class ArtworkResource extends JsonResource
|
||||
$viewerAward = null;
|
||||
$isOwner = $viewerId > 0 && $viewerId === (int) ($this->user?->id ?? 0);
|
||||
|
||||
$bookmarksCount = $this->hasTable('artwork_bookmarks')
|
||||
? (int) DB::table('artwork_bookmarks')->where('artwork_id', (int) $this->id)->count()
|
||||
: 0;
|
||||
$bookmarksCount = (int) DB::table('artwork_bookmarks')
|
||||
->where('artwork_id', (int) $this->id)
|
||||
->count();
|
||||
|
||||
if ($viewerId > 0) {
|
||||
if ($this->hasTable('artwork_likes')) {
|
||||
@@ -73,19 +73,17 @@ class ArtworkResource extends JsonResource
|
||||
->exists();
|
||||
}
|
||||
|
||||
if ($this->hasTable('artwork_bookmarks')) {
|
||||
$isBookmarked = DB::table('artwork_bookmarks')
|
||||
->where('user_id', $viewerId)
|
||||
->where('artwork_id', (int) $this->id)
|
||||
->exists();
|
||||
}
|
||||
$isBookmarked = DB::table('artwork_bookmarks')
|
||||
->where('user_id', $viewerId)
|
||||
->where('artwork_id', (int) $this->id)
|
||||
->exists();
|
||||
|
||||
$isFavorited = DB::table('artwork_favourites')
|
||||
->where('user_id', $viewerId)
|
||||
->where('artwork_id', (int) $this->id)
|
||||
->exists();
|
||||
|
||||
if (!empty($this->user?->id)) {
|
||||
if (! empty($this->user?->id)) {
|
||||
if ($this->hasTable('user_followers')) {
|
||||
$isFollowing = DB::table('user_followers')
|
||||
->where('user_id', (int) $this->user->id)
|
||||
@@ -100,7 +98,7 @@ class ArtworkResource extends JsonResource
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->group?->id) && $this->hasTable('group_follows')) {
|
||||
if (! empty($this->group?->id) && $this->hasTable('group_follows')) {
|
||||
$isFollowingGroup = DB::table('group_follows')
|
||||
->where('group_id', (int) $this->group->id)
|
||||
->where('user_id', $viewerId)
|
||||
@@ -125,7 +123,7 @@ class ArtworkResource extends JsonResource
|
||||
'id' => (int) ($user->id ?? 0),
|
||||
'name' => html_entity_decode((string) ($user->name ?? ''), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'username' => (string) ($user->username ?? ''),
|
||||
'profile_url' => ! empty($user->username) ? '/@' . $user->username : null,
|
||||
'profile_url' => ! empty($user->username) ? '/@'.$user->username : null,
|
||||
'avatar_url' => $user->profile?->avatar_url,
|
||||
];
|
||||
};
|
||||
@@ -150,7 +148,7 @@ class ArtworkResource extends JsonResource
|
||||
'slug' => (string) ($this->user?->username ?? ''),
|
||||
'headline' => '',
|
||||
'avatar_url' => $this->user?->profile?->avatar_url,
|
||||
'profile_url' => $this->user?->username ? '/@' . $this->user->username : null,
|
||||
'profile_url' => $this->user?->username ? '/@'.$this->user->username : null,
|
||||
'followers_count' => $followerCount,
|
||||
'follow_url' => null,
|
||||
'unfollow_url' => null,
|
||||
@@ -179,6 +177,8 @@ class ArtworkResource extends JsonResource
|
||||
'title' => $decode($this->title),
|
||||
'description' => $decode($this->description),
|
||||
'description_html' => $this->renderDescriptionHtml(),
|
||||
'download_status' => (string) ($this->download_status ?? 'unknown'),
|
||||
'download_source' => $this->download_source,
|
||||
'dimensions' => [
|
||||
'width' => (int) ($this->width ?? 0),
|
||||
'height' => (int) ($this->height ?? 0),
|
||||
@@ -201,7 +201,7 @@ class ArtworkResource extends JsonResource
|
||||
'id' => (int) ($this->user?->id ?? 0),
|
||||
'name' => html_entity_decode((string) ($this->user?->name ?? ''), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'username' => (string) ($this->user?->username ?? ''),
|
||||
'profile_url' => $this->user?->username ? '/@' . $this->user->username : null,
|
||||
'profile_url' => $this->user?->username ? '/@'.$this->user->username : null,
|
||||
'avatar_url' => $this->user?->profile?->avatar_url,
|
||||
'level' => (int) ($this->user?->level ?? 1),
|
||||
'rank' => (string) ($this->user?->rank ?? 'Newbie'),
|
||||
@@ -258,20 +258,40 @@ class ArtworkResource extends JsonResource
|
||||
'maturity' => app(ArtworkMaturityService::class)->presentation($this->resource, $request->user()),
|
||||
'evolution' => app(ArtworkEvolutionService::class)->publicPayload($this->resource, $request->user()),
|
||||
'world_participation' => $this->resolveWorldParticipation(),
|
||||
'categories' => $this->categories->map(fn ($category) => [
|
||||
'id' => (int) $category->id,
|
||||
'slug' => (string) $category->slug,
|
||||
'name' => html_entity_decode((string) $category->name, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'content_type_slug' => (string) ($category->contentType?->slug ?? ''),
|
||||
'url' => $category->contentType ? $category->url : null,
|
||||
'parent' => $category->parent ? [
|
||||
'id' => (int) $category->parent->id,
|
||||
'slug' => (string) $category->parent->slug,
|
||||
'name' => html_entity_decode((string) $category->parent->name, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'content_type_slug' => (string) ($category->parent->contentType?->slug ?? ''),
|
||||
'url' => $category->parent->contentType ? $category->parent->url : null,
|
||||
] : null,
|
||||
])->values(),
|
||||
'category' => ($primary = $this->resolvedPrimaryCategory()) ? [
|
||||
'id' => (int) $primary->id,
|
||||
'slug' => (string) $primary->slug,
|
||||
'name' => html_entity_decode((string) $primary->name, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'content_type_slug' => (string) ($primary->contentType?->slug ?? ''),
|
||||
'url' => $primary->contentType ? $primary->url : null,
|
||||
'is_primary' => true,
|
||||
] : null,
|
||||
'primary_category' => ($primary = $this->resolvedPrimaryCategory()) ? [
|
||||
'id' => (int) $primary->id,
|
||||
'slug' => (string) $primary->slug,
|
||||
'name' => html_entity_decode((string) $primary->name, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'content_type_slug' => (string) ($primary->contentType?->slug ?? ''),
|
||||
'url' => $primary->contentType ? $primary->url : null,
|
||||
] : null,
|
||||
'categories' => $this->categories->map(function ($category) {
|
||||
$primaryId = (int) ($this->primary_category_id ?? 0);
|
||||
|
||||
return [
|
||||
'id' => (int) $category->id,
|
||||
'slug' => (string) $category->slug,
|
||||
'name' => html_entity_decode((string) $category->name, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'content_type_slug' => (string) ($category->contentType?->slug ?? ''),
|
||||
'url' => $category->contentType ? $category->url : null,
|
||||
'is_primary' => $primaryId > 0 && (int) $category->id === $primaryId,
|
||||
'parent' => $category->parent ? [
|
||||
'id' => (int) $category->parent->id,
|
||||
'slug' => (string) $category->parent->slug,
|
||||
'name' => html_entity_decode((string) $category->parent->name, ENT_QUOTES | ENT_HTML5, 'UTF-8'),
|
||||
'content_type_slug' => (string) ($category->parent->contentType?->slug ?? ''),
|
||||
'url' => $category->parent->contentType ? $category->parent->url : null,
|
||||
] : null,
|
||||
];
|
||||
})->values(),
|
||||
'tags' => $this->tags->map(fn ($tag) => [
|
||||
'id' => (int) $tag->id,
|
||||
'slug' => (string) $tag->slug,
|
||||
@@ -282,10 +302,6 @@ class ArtworkResource extends JsonResource
|
||||
|
||||
private function resolveScreenshotAssets(): array
|
||||
{
|
||||
if (! $this->hasTable('artwork_files')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return DB::table('artwork_files')
|
||||
->where('artwork_id', (int) $this->id)
|
||||
->where('variant', 'like', 'shot%')
|
||||
@@ -296,9 +312,9 @@ class ArtworkResource extends JsonResource
|
||||
$url = $this->objectUrl($path);
|
||||
|
||||
return [
|
||||
'id' => (string) ($row->variant ?? ('shot' . ($index + 1))),
|
||||
'id' => (string) ($row->variant ?? ('shot'.($index + 1))),
|
||||
'variant' => (string) ($row->variant ?? ''),
|
||||
'label' => 'Screenshot ' . ($index + 1),
|
||||
'label' => 'Screenshot '.($index + 1),
|
||||
'url' => $url,
|
||||
'thumb_url' => $url,
|
||||
'mime_type' => (string) ($row->mime ?? 'image/jpeg'),
|
||||
@@ -319,7 +335,7 @@ class ArtworkResource extends JsonResource
|
||||
|
||||
$base = rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/');
|
||||
|
||||
return $base . '/' . $trimmedPath;
|
||||
return $base.'/'.$trimmedPath;
|
||||
}
|
||||
|
||||
private function renderDescriptionHtml(): string
|
||||
@@ -342,73 +358,75 @@ class ArtworkResource extends JsonResource
|
||||
$items = collect();
|
||||
$participationWorlds = collect();
|
||||
|
||||
if ($this->hasTable('world_relations') && $this->hasTable('worlds')) {
|
||||
$relations = WorldRelation::query()
|
||||
->with('world')
|
||||
->where('related_type', WorldRelation::TYPE_ARTWORK)
|
||||
->where('related_id', (int) $this->id)
|
||||
->get()
|
||||
->filter(fn (WorldRelation $relation): bool => $relation->world !== null && $relation->world->isPubliclyVisible())
|
||||
->values();
|
||||
$relations = WorldRelation::query()
|
||||
->with('world')
|
||||
->where('related_type', WorldRelation::TYPE_ARTWORK)
|
||||
->where('related_id', (int) $this->id)
|
||||
->get()
|
||||
->filter(fn (WorldRelation $relation): bool => $relation->world !== null && $relation->world->isPubliclyVisible())
|
||||
->values();
|
||||
|
||||
$participationWorlds = $participationWorlds->concat($relations->pluck('world')->filter());
|
||||
$participationWorlds = $participationWorlds->concat(
|
||||
$relations->pluck('world')->filter()
|
||||
);
|
||||
|
||||
$items = $items->concat(
|
||||
$relations->map(function (WorldRelation $relation): array {
|
||||
$world = $relation->world;
|
||||
$items = $items->concat(
|
||||
$relations->map(function (WorldRelation $relation): array {
|
||||
$world = $relation->world;
|
||||
|
||||
return [
|
||||
'world_id' => (int) $relation->world_id,
|
||||
'world_title' => (string) $world->title,
|
||||
'world_slug' => (string) $world->slug,
|
||||
'world_url' => $world->publicUrl(),
|
||||
'badge_label' => 'Part of ' . $world->title,
|
||||
'status' => 'curated',
|
||||
'status_label' => 'Curated',
|
||||
'tone' => 'curated',
|
||||
'sort_priority' => 1,
|
||||
];
|
||||
})
|
||||
);
|
||||
}
|
||||
return [
|
||||
'world_id' => (int) $relation->world_id,
|
||||
'world_title' => (string) $world->title,
|
||||
'world_slug' => (string) $world->slug,
|
||||
'world_url' => $world->publicUrl(),
|
||||
'badge_label' => 'Part of '.$world->title,
|
||||
'status' => 'curated',
|
||||
'status_label' => 'Curated',
|
||||
'tone' => 'curated',
|
||||
'sort_priority' => 1,
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
if ($this->hasTable('world_submissions')) {
|
||||
$liveSubmissions = $this->worldSubmissions
|
||||
->filter(function (WorldSubmission $submission): bool {
|
||||
return (string) $submission->status === WorldSubmission::STATUS_LIVE
|
||||
&& $submission->world !== null
|
||||
&& $submission->world->isPubliclyVisible();
|
||||
})
|
||||
->values();
|
||||
$liveSubmissions = $this->worldSubmissions
|
||||
->filter(function (WorldSubmission $submission): bool {
|
||||
return (string) $submission->status === WorldSubmission::STATUS_LIVE
|
||||
&& $submission->world !== null
|
||||
&& $submission->world->isPubliclyVisible();
|
||||
})
|
||||
->values();
|
||||
|
||||
$participationWorlds = $participationWorlds->concat($liveSubmissions->pluck('world')->filter());
|
||||
World::primeCanonicalEditionIds($participationWorlds->pluck('recurrence_key')->all());
|
||||
$participationWorlds = $participationWorlds->concat(
|
||||
$liveSubmissions->pluck('world')->filter()
|
||||
);
|
||||
|
||||
$items = $items->concat(
|
||||
$liveSubmissions->map(function (WorldSubmission $submission): array {
|
||||
$world = $submission->world;
|
||||
$isFeatured = (bool) $submission->is_featured;
|
||||
World::primeCanonicalEditionIds(
|
||||
$participationWorlds->pluck('recurrence_key')->all()
|
||||
);
|
||||
|
||||
return [
|
||||
'world_id' => (int) $submission->world_id,
|
||||
'world_title' => (string) $world->title,
|
||||
'world_slug' => (string) $world->slug,
|
||||
'world_url' => $world->publicUrl(),
|
||||
'badge_label' => ($isFeatured ? 'Featured in ' : 'Part of ') . $world->title,
|
||||
'status' => (string) $submission->status,
|
||||
'status_label' => $isFeatured ? 'Featured' : 'Community submission',
|
||||
'tone' => $isFeatured ? 'featured' : 'community',
|
||||
'sort_priority' => $isFeatured ? 0 : 2,
|
||||
];
|
||||
})
|
||||
);
|
||||
} elseif ($participationWorlds->isNotEmpty()) {
|
||||
World::primeCanonicalEditionIds($participationWorlds->pluck('recurrence_key')->all());
|
||||
}
|
||||
$items = $items->concat(
|
||||
$liveSubmissions->map(function (WorldSubmission $submission): array {
|
||||
$world = $submission->world;
|
||||
$isFeatured = (bool) $submission->is_featured;
|
||||
|
||||
if ($this->hasTable('world_reward_grants')) {
|
||||
$items = $items->concat(app(WorldRewardService::class)->artworkRewardBadges($this->resource));
|
||||
}
|
||||
return [
|
||||
'world_id' => (int) $submission->world_id,
|
||||
'world_title' => (string) $world->title,
|
||||
'world_slug' => (string) $world->slug,
|
||||
'world_url' => $world->publicUrl(),
|
||||
'badge_label' => ($isFeatured ? 'Featured in ' : 'Part of ').$world->title,
|
||||
'status' => (string) $submission->status,
|
||||
'status_label' => $isFeatured ? 'Featured' : 'Community submission',
|
||||
'tone' => $isFeatured ? 'featured' : 'community',
|
||||
'sort_priority' => $isFeatured ? 0 : 2,
|
||||
];
|
||||
})
|
||||
);
|
||||
|
||||
$items = $items->concat(
|
||||
app(WorldRewardService::class)
|
||||
->artworkRewardBadges($this->resource)
|
||||
);
|
||||
|
||||
return $items
|
||||
->sortBy('sort_priority')
|
||||
|
||||
Reference in New Issue
Block a user